更改为FireDAC后,我无法使此代码在MSSQL / Oracle上运行:
with DataFormsettings do
begin
Close;
if Params.Count=0 then FetchParams;
Params.ParamByName('TT_EMP_ID').Asinteger := AEmpID;
Params.ParamByName('TT_FORM').AString := UpperCase(AKey);
Open;
if (RecordCount>0) then
S := FieldByName('TT_VIEWDATA').Asstring;
end;
AKey和S都是字符串。 Open语句给出错误
[FireDAC][Phys][MSSQL]-338 Param type changed from [ftString] to [ftWidestring]
[FireDAC][Phys][Ora]-338 Param type changed from [ftString] to [ftWidestring]
连接到MSSQL或Oracle数据库时;连接到FireBird时没有。
在FetchParams
之后,DataFormsettings.params[1].datatype
总是一个ftString
。
如果我更换
Params.ParamByName('TT_FORM').AString := UpperCase(AKey);
同
Params.ParamByName('TT_FORM').Value := UpperCase(AKey);
... Open语句中没有错误。虽然我没有真正理解错误,但我认为已经解决了它。毕竟,这应该是所有默认的Delphi String类型...... 但是现在,对于Oracle(而不是FireBird或MSSQL)而言,S分配失败,因为我看到2字节字符被返回。 S包含:
\'#0'S'#0'o'#0'f'#0't'#0'w'#0'a'#0'r'#0'e'#0'\'#0'T'#0'i'#0'm'#0'e'#0'T'#0'e'#0'l'#0'l'#0'...
我可以用例如
S := TEncoding.Unicode.GetString(FieldByName('TT_VIEWDATA').AsBytes);
对于Oracle,但(当然)在使用其他两种不起作用的数据库类型时:
No mapping for the Unicode character exists in the target multi-byte code page
我在这里错过了什么?具体来说,我想让AsString检索/分配工作。 请注意,设置AsString属性会在FireDAC TFDParam.AsString documentation中将DataType属性设置为ftWideString或ftString remark。看起来好像参数值赋值只是将类型从ftString切换到ftWideString(由原始错误指示)。
DataFormSettings
是客户端应用程序中的TClientDataSet
,连接到TDataSetProvider
和TFDQuery
所在的服务器应用程序。查询是
select
TT_FORMSETTINGS_ID,
TT_EMP_ID,
TT_FORM,
TT_VERSION,
TT_VIEWDATA
from TT_FORMSETTINGS
where TT_EMP_ID=:TT_EMP_ID
and TT_FORM=:TT_FORM
表格创建如下:
火鸟:
CREATE TABLE TT_FORMSETTINGS
(
TT_FORMSETTINGS_ID INTEGER DEFAULT 0 NOT NULL,
TT_EMP_ID INTEGER,
TT_FORM VARCHAR(50),
TT_VERSION INTEGER,
TT_VIEWDATA BLOB SUB_TYPE TEXT SEGMENT SIZE 80,
TT_TAG INTEGER,
TT_TAGTYPE INTEGER,
TT_TAGDATE TIMESTAMP
);
甲骨文:
CREATE TABLE TT_FORMSETTINGS
(
TT_FORMSETTINGS_ID NUMBER(10,0) DEFAULT 0 NOT NULL,
TT_EMP_ID NUMBER(10,0),
TT_FORM VARCHAR(50),
TT_VERSION NUMBER(10,0),
TT_VIEWDATA CLOB,
TT_TAG NUMBER(10,0),
TT_TAGTYPE NUMBER(10,0),
TT_TAGDATE DATE
);
MSSQL:
CREATE TABLE TT_FORMSETTINGS
(
TT_FORMSETTINGS_ID INTEGER NOT NULL CONSTRAINT TT_C0_FORMSETTINGS DEFAULT 0,
TT_EMP_ID INTEGER NULL,
TT_FORM VARCHAR(50) NULL,
TT_VERSION INTEGER NULL,
TT_VIEWDATA TEXT NULL,
TT_TAG INTEGER NULL,
TT_TAGTYPE INTEGER NULL,
TT_TAGDATE DATETIME NULL
);
我已经检查过TT_VIEWDATA
在所有数据库中包含正确的数据;它是一个包含CRLF的长字符串:
\Software\TimeTell\Demo8\Forms\TFormTileMenu'#$D#$A'Version,1,80502'#$D#$A'\Software\TimeTell\Demo8\Forms\TFormTileMenu\TileControlMenu'#$D#$A'\Software\TimeTell\Demo8\Forms\TFormTileMenu\TileControlMenu\FormTileMenu.TileControlMenu'#$D#$A'Version,4,2'#$D#$A'\Software\TimeTell\Demo8\Forms\TFormTileMenu\TileControlMenu\FormTileMenu.TileControlMenu...
笔记:
select * from NLS_database_PARAMETERS where parameter like '%CHARACTERSET%'
返回NLS_CHARACTERSET=WE8MSWIN1252
和NLS_NCHAR_CHARACTERSET=AL16UTF16
查询SELECT dump(dbms_lob.substr(tt_viewdata,100,1), 1016), tt_viewdata FROM tt_formsettings
确认CLOB包含Win1252代码页的ASCII字节:
Typ=1 Len=100 CharacterSet=WE8MSWIN1252: 5c,53,6f,66,74,77,61,72,65,5c,54,69,6d,65,54,65,6c,6c,5c,44,65,...
FieldByName().AsANSIString
给出与FieldByName().AsString
相同的结果附加信息:这是一个遗留应用程序,在DataFormsettings
TClientDataset
上具有持久字段定义。 TT_VIEWDATA
被定义为TMemoField
:
DataFormsettingsTT_VIEWDATA: TMemoField;
在一个小的testapp(直接连接到Oracle;而不是客户端 - 服务器)我让Delphi添加字段定义然后它说:
DataFormsettingsTT_VIEWDATA: TWideMemoField;
如果我在主应用程序中使用它,Oracle工作正常,但后来我得到MSSQL的“垃圾”。
我还尝试为Oracle连接设置映射规则,如(许多变体):
with AConnection.FormatOptions.MapRules.Add do
begin
SourceDataType := dtWideMemo;
TargetDataType := dtMemo;
end;
AConnection.FormatOptions.OwnMapRules := true;
但这没有帮助。
这是它不起作用的原因:
在FireDAC.Stan.Option
:
procedure TFDFormatOptions.ColumnDef2FieldDef()
...
dtWideHMemo:
// Here was ftOraClob, but then will be created TMemoField,
// which does not know anything about Unicode. So, I have
// changed to ftFmtMemo. But probably may be problems ...
ADestFieldType := ftWideMemo;
的确,可能是问题。
解决方案是添加将dtWideHMemo
转换为dtMemo
的映射规则。
之后,读取和写入CLOB .AsString
工作正常。
在Embarcadero Quality Portal报道为RSP-19600。
为了完整性:因为我的其他答案中提到的映射不再有效,您必须使用.Value
而不是.AsString.
更改对参数的访问权限
这不是一个明确的解决方案,请参阅代码块之前的最后一个注释。它仍然像一个黑客。我不是将它添加到问题中(因为'尝试'),因为最终这会起作用。
有两件事情正在发生,他们都可以通过以下更改来解决:
FieldByName().AsString
检索/分配不起作用请注意,我在整个应用程序中受设计时字段定义的限制,该应用程序必须处理所有三种数据库类型,特别是DataFormSettingsTT_VIEWDATA
持久字段是TMemoField
。
使用问题底部提到的表定义,如果您设置了TFDConnection -> TFDQuery -> TDataSetProvider -> TClientDataSet
并使用Add all fields添加字段定义,则DataFormSettingsTT_VIEWDATA
将是以下类型:
TMemoField
与BlobType=ftMemoField
for FireBirdTMemoField
与BlobType=ftWideMemoField
for MSSQLTWideMemoField
与BlobType=ftWideMemoField
for Oracle。手动编辑.DFM和.PAS以将Oracle TWideMemoField
设置回TMemoField
工作(好吧,我不必更改它,它是遗留代码)如果我还:
BlobType=ftWideMemoField
设计时间TMemoField
s(我可以在我的所有数据模块下降的父级中的OnCreate中执行此操作);TEncoding.Unicode.GetString(FieldByName(SFormSettingsViewData).AsBytes)
处理Oracle的字符串检索。但这仍然不是最佳的。我的客户端代码与TClientDataSet will now have to know what kind of database it is。我在客户端应用程序中有手段查询服务器。
以下是包含以下更改的示例应用:
uFireDacOracleBlob.pas
文件:
unit uFireDacOracleBlob;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.Oracle,
FireDAC.Phys.OracleDef, FireDAC.VCLUI.Wait, FireDAC.Stan.Param, FireDAC.DatS,
FireDAC.DApt.Intf, FireDAC.DApt, Datasnap.DBClient, Datasnap.Provider,
Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client, Vcl.StdCtrls, Vcl.ExtCtrls,
FireDAC.Phys.MSSQL, FireDAC.Phys.MSSQLDef, FireDAC.Phys.IB,
FireDAC.Phys.IBDef, FireDAC.Phys.FBDef, FireDAC.Phys.IBBase, FireDAC.Phys.FB,
FireDAC.Phys.ODBCBase;
type
TFrmFireDacOracleBlob = class(TForm)
FDConnection1: TFDConnection;
FDPhysOracleDriverLink1: TFDPhysOracleDriverLink;
FDQuery1: TFDQuery;
DataSetProvider1: TDataSetProvider;
ClientDataSet1: TClientDataSet;
Edit0: TEdit;
Label1: TLabel;
LblPos0: TLabel;
RGpDB: TRadioGroup;
BtnOpen: TButton;
FDConnection2: TFDConnection;
FDQuery2: TFDQuery;
DataSetProvider2: TDataSetProvider;
ClientDataSet2: TClientDataSet;
FDConnection0: TFDConnection;
FDQuery0: TFDQuery;
DataSetProvider0: TDataSetProvider;
ClientDataSet0: TClientDataSet;
FDPhysMSSQLDriverLink1: TFDPhysMSSQLDriverLink;
FDPhysFBDriverLink1: TFDPhysFBDriverLink;
ClientDataSet0TT_FORMSETTINGS_ID: TIntegerField;
ClientDataSet0TT_EMP_ID: TIntegerField;
ClientDataSet0TT_FORM: TStringField;
ClientDataSet0TT_VERSION: TIntegerField;
ClientDataSet0TT_VIEWDATA: TMemoField;
ClientDataSet1TT_FORMSETTINGS_ID: TIntegerField;
ClientDataSet1TT_EMP_ID: TIntegerField;
ClientDataSet1TT_FORM: TStringField;
ClientDataSet1TT_VERSION: TIntegerField;
ClientDataSet1TT_VIEWDATA: TMemoField;
ClientDataSet2TT_FORMSETTINGS_ID: TIntegerField;
ClientDataSet2TT_EMP_ID: TIntegerField;
ClientDataSet2TT_FORM: TStringField;
ClientDataSet2TT_VERSION: TIntegerField;
ClientDataSet2TT_VIEWDATA: TMemoField;
BtnSet: TButton;
Label2: TLabel;
LblPos1: TLabel;
Edit1: TEdit;
Label4: TLabel;
LblPos2: TLabel;
Edit2: TEdit;
BtnParam: TButton;
procedure BtnOpenClick(Sender: TObject);
procedure BtnSetClick(Sender: TObject);
procedure BtnParamClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FStrFirebird,
FStrOracle,
FStrMSSQL :String;
procedure ShowString(AStr: String; ALbl: TLabel; AEdit: TEdit);
public
end;
var
FrmFireDacOracleBlob: TFrmFireDacOracleBlob;
implementation
{$R *.dfm}
const
cSQLText = 'select TT_FORMSETTINGS_ID,TT_EMP_ID,TT_FORM,TT_VERSION,TT_VIEWDATA from TT_FORMSETTINGS where TT_EMP_ID=:TT_EMP_ID and TT_FORM=:TT_FORM';
procedure TFrmFireDacOracleBlob.BtnParamClick(Sender: TObject);
begin
case RGpDB.ItemIndex of
0: begin
FDQuery0.SQL.Text := cSQLText;
with ClientDataSet0 do
begin
if Params.Count=0 then FetchParams;
Params.ParamByName('TT_EMP_ID').Asinteger := 1;
Params.ParamByName('TT_FORM').AsString := 'TFORMTILEMENU';
Open;
if (RecordCount>0) then
FStrFirebird := FieldByName('TT_VIEWDATA').Asstring;
ShowString(FStrFireBird,LblPos0,Edit0);
end;
end;
1: begin
FDQuery1.SQL.Text := cSQLText;
with ClientDataSet1 do
begin
if Params.Count=0 then FetchParams;
Params.ParamByName('TT_EMP_ID').Asinteger := 1;
Params.ParamByName('TT_FORM').AsString := 'TFORMTILEMENU';
Open;
if (RecordCount>0) then
// FStrOracle := FieldByName('TT_VIEWDATA').Value;
FStrOracle := TEncoding.Unicode.GetString(FieldByName('tt_viewdata').AsBytes);
ShowString(FStrOracle,LblPos1,Edit1);
end;
end;
2: begin
FDQuery2.SQL.Text := cSQLText;
with ClientDataSet2 do
begin
if Params.Count=0 then FetchParams;
Params.ParamByName('TT_EMP_ID').Asinteger := 1;
Params.ParamByName('TT_FORM').AsString := 'TFORMTILEMENU';
Open;
if (RecordCount>0) then
FStrMSSQL := FieldByName('TT_VIEWDATA').Asstring;
ShowString(FStrMSSQL,LblPos2,Edit2);
end;
end;
end;
end;
procedure TFrmFireDacOracleBlob.BtnSetClick(Sender: TObject);
begin
case RGpDB.ItemIndex of
0: begin
FStrFirebird := FStrFirebird + #13#10'Added another line';
ClientDataSet0.Edit;
ClientDataSet0.FieldByName('tt_viewdata').Value := FStrFireBird;
ClientDataSet0.ApplyUpdates(0);
end;
1: begin
FStrOracle := FStrOracle + #13#10'Added another line';
ClientDataSet1.Edit;
// ClientDataSet1.FieldByName('tt_viewdata').AsString := FStrOracle; // does not work
// ClientDataSet1.FieldByName('tt_viewdata').Value := FStrOracle; // does not work
ClientDataSet1.FieldByName('tt_viewdata').Value := TEncoding.Unicode.GetBytes(FStrOracle);
// ClientDataSet1.FieldByName('tt_viewdata').AsBytes := TEncoding.Unicode.GetBytes(FStrOracle); Also works
ClientDataSet1.ApplyUpdates(0);
end;
2: begin
FStrMSSQL := FStrMSSQL + #13#10'Added another line';
ClientDataSet2.Edit;
ClientDataSet2.FieldByName('tt_viewdata').AsString := FStrFireBird;
ClientDataSet2.ApplyUpdates(0);
end;
end;
end;
procedure TFrmFireDacOracleBlob.FormCreate(Sender: TObject);
var i: integer;
begin
for i := 0 to self.ComponentCount-1 do
if (self.Components[i] is TMemoField) then
(self.Components[i] as TMemoField).BlobType := ftWideMemo;
end;
procedure TFrmFireDacOracleBlob.ShowString(AStr: String; ALbl: TLabel; AEdit: TEdit);
begin
ALbl.Caption := IntToStr(Pos(#13#10,AStr));
AEdit.Text := AStr;
end;
procedure TFrmFireDacOracleBlob.BtnOpenClick(Sender: TObject);
begin
case RGpDB.ItemIndex of
0: begin
// SetFireBirdMapRules(FDConnection1); Design time
ClientDataSet0.Open;
FStrFirebird := ClientDataSet0.FieldByName('tt_viewdata').AsString;
ShowString(FStrFireBird,LblPos0,Edit0);
end;
1: begin
// SetOracleMapRules(FDConnection1); Design time
ClientDataSet1.Open;
// FStrOracle := ClientDataSet1.FieldByName('tt_viewdata').AsString;
FStrOracle := TEncoding.Unicode.GetString(ClientDataSet1.FieldByName('tt_viewdata').AsBytes);
ShowString(FStrOracle,LblPos1,Edit1);
end;
2: begin
// SetMSSQLMapRules(FDConnection1); Design time
ClientDataSet2.Open;
FStrMSSQL := ClientDataSet2.FieldByName('tt_viewdata').AsString;
ShowString(FStrMSSQL,LblPos2,Edit2);
end;
end;
end;
end.
uFireDacOracleBlob.dfm
文件:
object FrmFireDacOracleBlob: TFrmFireDacOracleBlob
Left = 0
Top = 0
Caption = 'FireDac and Oracle Clobs'
ClientHeight = 278
ClientWidth = 577
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
Position = poScreenCenter
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 32
Top = 161
Width = 91
Height = 13
Caption = 'Position first CRLF:'
end
object LblPos0: TLabel
Left = 128
Top = 161
Width = 6
Height = 13
Caption = '0'
end
object Label2: TLabel
Left = 32
Top = 203
Width = 91
Height = 13
Caption = 'Position first CRLF:'
end
object LblPos1: TLabel
Left = 128
Top = 203
Width = 6
Height = 13
Caption = '0'
end
object Label4: TLabel
Left = 32
Top = 245
Width = 91
Height = 13
Caption = 'Position first CRLF:'
end
object LblPos2: TLabel
Left = 128
Top = 245
Width = 6
Height = 13
Caption = '0'
end
object Edit0: TEdit
Left = 32
Top = 138
Width = 505
Height = 21
TabOrder = 0
end
object RGpDB: TRadioGroup
Left = 32
Top = 8
Width = 249
Height = 33
Columns = 3
ItemIndex = 0
Items.Strings = (
'FireBird'
'Oracle'
'MSSQL')
TabOrder = 1
end
object BtnOpen: TButton
Left = 32
Top = 56
Width = 75
Height = 25
Caption = 'Open Table'
TabOrder = 2
OnClick = BtnOpenClick
end
object BtnSet: TButton
Left = 120
Top = 56
Width = 75
Height = 25
Caption = 'Update field'
TabOrder = 3
OnClick = BtnSetClick
end
object Edit1: TEdit
Left = 32
Top = 180
Width = 505
Height = 21
TabOrder = 4
end
object Edit2: TEdit
Left = 32
Top = 222
Width = 505
Height = 21
TabOrder = 5
end
object BtnParam: TButton
Left = 32
Top = 96
Width = 104
Height = 25
Caption = 'Open with params'
TabOrder = 6
OnClick = BtnParamClick
end
object FDConnection1: TFDConnection
Params.Strings = (
'User_Name=testv4'
'Password=testv4'
'Database=VS2003-2005-10'
'DriverID=Ora')
FormatOptions.AssignedValues = [fvMapRules]
FormatOptions.OwnMapRules = True
FormatOptions.MapRules = <
item
SourceDataType = dtBCD
TargetDataType = dtInt32
end
item
SourceDataType = dtFmtBCD
TargetDataType = dtDouble
end>
Connected = True
LoginPrompt = False
Left = 312
Top = 72
end
object FDPhysOracleDriverLink1: TFDPhysOracleDriverLink
Left = 368
Top = 72
end
object FDQuery1: TFDQuery
Connection = FDConnection1
SQL.Strings = (
'select * from tt_formsettings')
Left = 416
Top = 72
end
object DataSetProvider1: TDataSetProvider
DataSet = FDQuery1
Left = 464
Top = 72
end
object ClientDataSet1: TClientDataSet
Aggregates = <>
Params = <>
ProviderName = 'DataSetProvider1'
Left = 512
Top = 72
object ClientDataSet1TT_FORMSETTINGS_ID: TIntegerField
FieldName = 'TT_FORMSETTINGS_ID'
Required = True
end
object ClientDataSet1TT_EMP_ID: TIntegerField
FieldName = 'TT_EMP_ID'
end
object ClientDataSet1TT_FORM: TStringField
FieldName = 'TT_FORM'
Size = 50
end
object ClientDataSet1TT_VERSION: TIntegerField
FieldName = 'TT_VERSION'
end
object ClientDataSet1TT_VIEWDATA: TMemoField
FieldName = 'TT_VIEWDATA'
BlobType = ftWideMemo
end
end
object FDConnection2: TFDConnection
Params.Strings = (
'Database=test'
'Password=test'
'User_Name=test'
'Server=VS2003-2008'
'DriverID=MSSQL')
FormatOptions.AssignedValues = [fvMapRules]
FormatOptions.OwnMapRules = True
FormatOptions.MapRules = <
item
SourceDataType = dtDateTimeStamp
TargetDataType = dtDateTime
end>
Connected = True
LoginPrompt = False
Left = 312
Top = 144
end
object FDQuery2: TFDQuery
Connection = FDConnection2
SQL.Strings = (
'select * from tt_formsettings')
Left = 416
Top = 144
end
object DataSetProvider2: TDataSetProvider
DataSet = FDQuery2
Left = 464
Top = 144
end
object ClientDataSet2: TClientDataSet
Aggregates = <>
Params = <>
ProviderName = 'DataSetProvider2'
Left = 512
Top = 144
object ClientDataSet2TT_FORMSETTINGS_ID: TIntegerField
FieldName = 'TT_FORMSETTINGS_ID'
Required = True
end
object ClientDataSet2TT_EMP_ID: TIntegerField
FieldName = 'TT_EMP_ID'
end
object ClientDataSet2TT_FORM: TStringField
FieldName = 'TT_FORM'
Size = 50
end
object ClientDataSet2TT_VERSION: TIntegerField
FieldName = 'TT_VERSION'
end
object ClientDataSet2TT_VIEWDATA: TMemoField
FieldName = 'TT_VIEWDATA'
BlobType = ftMemo
end
end
object FDConnection0: TFDConnection
Params.Strings = (
'Database=D:\Testing\Diverse\FireDacOracleBlob\TIMETELL_DEMO.GDB'
'User_Name=SYSDBA'
'Password=masterkey'
'DriverID=IB')
FormatOptions.AssignedValues = [fvMapRules]
FormatOptions.OwnMapRules = True
FormatOptions.MapRules = <
item
SourceDataType = dtDateTimeStamp
TargetDataType = dtDateTime
end
item
SourceDataType = dtSingle
TargetDataType = dtDouble
end>
Connected = True
LoginPrompt = False
Left = 312
Top = 8
end
object FDQuery0: TFDQuery
Connection = FDConnection0
SQL.Strings = (
'select * from tt_formsettings')
Left = 416
Top = 8
end
object DataSetProvider0: TDataSetProvider
DataSet = FDQuery0
Left = 464
Top = 8
end
object ClientDataSet0: TClientDataSet
Aggregates = <>
Params = <>
ProviderName = 'DataSetProvider0'
Left = 512
Top = 8
object ClientDataSet0TT_FORMSETTINGS_ID: TIntegerField
FieldName = 'TT_FORMSETTINGS_ID'
Required = True
end
object ClientDataSet0TT_EMP_ID: TIntegerField
FieldName = 'TT_EMP_ID'
end
object ClientDataSet0TT_FORM: TStringField
FieldName = 'TT_FORM'
Size = 50
end
object ClientDataSet0TT_VERSION: TIntegerField
FieldName = 'TT_VERSION'
end
object ClientDataSet0TT_VIEWDATA: TMemoField
FieldName = 'TT_VIEWDATA'
BlobType = ftMemo
end
end
object FDPhysMSSQLDriverLink1: TFDPhysMSSQLDriverLink
Left = 368
Top = 144
end
object FDPhysFBDriverLink1: TFDPhysFBDriverLink
Left = 368
Top = 8
end
end
注意:现在(也)参数赋值的工作原理在Data Type Mapping (FireDAC)文档中:
在结果集列的情况下,每个规则定义由驱动程序返回的源数据类型到应用程序首选的目标数据类型的转换。在命令参数的情况下,该规则定义由应用程序指定的目标数据类型到由驱动程序支持的源数据类型的转换。所有规则(不包括基于名称的规则)都适用于两种情况。