Delphi ADO BCD 十进制字段值设置不正确
Posted
技术标签:
【中文标题】Delphi ADO BCD 十进制字段值设置不正确【英文标题】:Delphi ADO BCD Decimal field value set inproperly 【发布时间】:2021-05-27 11:04:30 【问题描述】:我在 Embarcadero 质量门户上创建了一个bug。但也许有人有一种解决方法可以让测试项目在这些标记上工作:
连接机制还是AdoConnection + AdoTable 发布到数据库的值等于从数据库中选择的值 TestField 类型未更改(Decimal(15, 3)) 用户不必重新配置区域设置。问题详情
影响版本/s:XE7、10.2 Tokyo Release 3、10.3 Rio Release 3 内部版本号:Delphi 10.3 版本 26.0.36039.7899 平台:Windows 10当您使用 Windows regional settings 时会出现该错误:点作为分组符号,逗号作为小数分隔符。
当您尝试使用 AdoConnection + AdoTable 将值设置为 MS-ACCESS 数据库中的 Decimal 字段时,您会得到实际值乘以比例,即:您将 MDB 中的 Decimal(15,3) 字段设置为 12,354,您将获得 12354。
测试项目 https://github.com/IgorKaplya/AdoBcdBug
unit Unit1;
interface
uses
System.SysUtils, System.Variants, System.Classes,
Vcl.Controls, Vcl.Forms, Vcl.StdCtrls,
Data.DB, FireDAC.Comp.Client, Data.Win.ADODB, 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.MSAcc,
FireDAC.Phys.MSAccDef, FireDAC.VCLUI.Wait, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt,
FireDAC.Comp.DataSet;
type
TForm1 = class(TForm)
conFDac: TFDConnection;
TableFireDac: TFDTable;
mmLog: TMemo;
conAdo: TADOConnection;
TableAdo: TADOTable;
btnTestBcdAdo: TButton;
btnBcdTestFDac: TButton;
btnSimpleAdo: TButton;
btnSimpleFDac: TButton;
procedure btnBcdTestFDacClick(Sender: TObject);
procedure btnSimpleAdoClick(Sender: TObject);
procedure btnSimpleFDacClick(Sender: TObject);
procedure btnTestBcdAdoClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FTestField: string;
FTestTable: string;
procedure AddInvitationMessage;
procedure EnsureTestTableExists;
procedure InitializeAdoConnection;
procedure InitializeFDacConnection;
procedure SetupTableComponents;
procedure TestBCD(const ATable: TDataSet);
procedure TestSimple(const ATable: TDataSet);
Private declarations
public
property TestField: string read FTestField write FTestField;
property TestTable: string read FTestTable write FTestTable;
Public declarations
end;
var
Form1: TForm1;
implementation
uses
Data.FmtBcd;
$R *.dfm
procedure TForm1.FormCreate(Sender: TObject);
begin
AddInvitationMessage();
InitializeAdoConnection;
EnsureTestTableExists();
InitializeFDacConnection;
SetupTableComponents();
end;
procedure TForm1.AddInvitationMessage;
const
description_message =
'Hi, the bug appears when you have Windows regional settings: dot as grouping symbol and comma as decimal sepearator';
affected_message: array[Boolean] of string = ('NOT AFFECTED', 'AFFECTED');
var
systemIsAffected: Boolean;
begin
mmLog.Lines.Add(description_message);
mmLog.Lines.Add(Format(' Your grouping symbol: %s', [QuotedStr(FormatSettings.ThousandSeparator)]));
mmLog.Lines.Add(Format(' Your decimal separator: %s', [QuotedStr(FormatSettings.DecimalSeparator)]));
systemIsAffected :=
SameText(FormatSettings.ThousandSeparator, '.') and
SameText(FormatSettings.DecimalSeparator, ',');
mmLog.Lines.Add('This system should be '+ affected_message[systemIsAffected]);
end;
procedure TForm1.btnBcdTestFDacClick(Sender: TObject);
begin
TestBCD(TableFireDac);
end;
procedure TForm1.btnSimpleAdoClick(Sender: TObject);
begin
TestSimple(TableAdo);
end;
procedure TForm1.btnSimpleFDacClick(Sender: TObject);
begin
TestSimple(TableFireDac);
end;
procedure TForm1.InitializeFDacConnection;
begin
conFDac.DriverName := 'MSAcc';
conFDac.Params.Database := '.\TestBase.mdb';
conFDac.Connected := True;
TableFireDac.Connection := conFDac;
end;
procedure TForm1.InitializeAdoConnection;
const
ado_jet_connection_string =
'Provider=Microsoft.Jet.OLEDB.4.0;'+
'Data Source=TestBase.mdb;'+
'Mode=ReadWrite;'+
'Persist Security Info=False;';
ado_ace_connection_string =
'Provider=Microsoft.ACE.OLEDB.12.0;'+
'Data Source=TestBase.mdb;'+
'Mode=ReadWrite;'+
'Persist Security Info=False;';
begin
conAdo.ConnectionString := ado_ace_connection_string;
conAdo.Connected := True;
TableAdo.Connection := conAdo;
end;
procedure TForm1.EnsureTestTableExists;
var
dummy: Integer;
tables: TStringList;
begin
TestTable := 'test_table';
TestField := 'test_field';
tables := TStringList.Create;
tables.Sorted := true;
try
conAdo.GetTableNames(tables);
if tables.Find(TestTable, dummy) then
conAdo.Execute(format('drop table %s', [TestTable]));
conAdo.Execute(format('create table %s (%s decimal(15,3))', [TestTable, TestField]));
finally
tables.Free;
end;
end;
procedure TForm1.SetupTableComponents;
begin
TableAdo.TableName := TestTable;
TableFireDac.TableName := TestTable;
end;
procedure TForm1.TestBCD(const ATable: TDataSet);
var
doubleValue: Double;
bcd, normalizedBcd: TBCD;
postedValue, reloadedValue: string;
begin
mmLog.Lines.Add('');
mmLog.Lines.Add('TestBCD: '+ATable.Name);
doubleValue := 12.34567;
mmLog.Lines.Add('Double: '+ doubleValue.ToString);
bcd := DoubleToBcd(doubleValue);
mmLog.Lines.Add('BCD: '+ BcdToStr(bcd));
NormalizeBcd(bcd, normalizedBcd, 15, 3);
mmLog.Lines.Add('Normalized BCD: '+BcdToStr(normalizedBcd));
ATable.Open;
ATable.Insert;
ATable.FieldByName(TestField).AsBCD := normalizedBcd;
ATable.Post;
postedValue := ATable.FieldByName(TestField).AsString;
mmLog.Lines.Add('Posted: ' + postedValue);
ATable.Close;
ATable.Open;
ATable.Last;
reloadedValue := ATable.FieldByName(TestField).AsString;
mmLog.Lines.Add('Reloaded: '+reloadedValue);
Assert(reloadedValue.Equals(postedValue), 'Reloaded value is not equal to posted.');
end;
procedure TForm1.btnTestBcdAdoClick(Sender: TObject);
begin
TestBCD(TableAdo);
end;
procedure TForm1.TestSimple(const ATable: TDataSet);
var
stringValue: String;
postedValue, reloadedValue: string;
begin
mmLog.Lines.Add('');
mmLog.Lines.Add('TestSimple: '+ATable.Name);
stringValue := '12,345';
mmLog.Lines.Add('String: '+ stringValue);
ATable.Open;
ATable.Insert;
ATable[TestField] := stringValue;
ATable.Post;
postedValue := ATable.FieldByName(TestField).AsString;
mmLog.Lines.Add('Posted: ' + postedValue);
ATable.Close;
ATable.Open;
ATable.Last;
reloadedValue := ATable.FieldByName(TestField).AsString;
mmLog.Lines.Add('Reloaded: '+reloadedValue);
Assert(reloadedValue.Equals(postedValue), 'Reloaded value is not equal to posted.');
end;
end.
FireDac MSAcc 驱动程序不符合此错误。
该错误出现在 Microsoft.Jet.OLEDB.4.0、Microsoft.ACE.OLEDB.12.0 两个提供商上。
Visual Basic for Access 似乎工作正常
mmLog中的输出是
Hi, the bug appears when you have Windows regional settings: dot as grouping symbol and comma as decimal sepearator
Your grouping symbol: '.'
Your decimal separator: ','
This system should be AFFECTED
TestBCD: TableAdo
Double: 12,34567
BCD: 12,34567
Normalized BCD: 12,345
Posted: 12,345
Reloaded: 12345
TestBCD: TableFireDac
Double: 12,34567
BCD: 12,34567
Normalized BCD: 12,345
Posted: 12,345
Reloaded: 12,345
TestSimple: TableAdo
String: 12,345
Posted: 12,345
Reloaded: 12345
TestSimple: TableFireDac
String: 12,345
Posted: 12,345
Reloaded: 12,345
存储在数据库中的实际值
对于 ADO:12345 对于 FireDac:12,345这段 VBA 代码运行良好(可以在 https://github.com/IgorKaplya/AdoBcdBug 的 TestVBA.mdb 中找到)
Public Function ConnectToOtherDB() As ADODB.Connection
Dim conn As ADODB.Connection
Set conn = New ADODB.Connection
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & CurrentProject.Path & "\TestVBA_OtherBase.mdb;" & _
"Mode=ReadWrite;" & _
"Persist Security Info=False;"
Set ConnectToOtherDB = conn
End Function
Public Sub TestBCD()
Dim conn As ADODB.Connection
Set conn = ConnectToOtherDB()
conn.Execute "drop table test_table"
conn.Execute "create table test_table (test_field decimal(15,3))"
conn.Close
conn.Open
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.Open "test_table", conn, adOpenDynamic, adLockOptimistic, adCmdTable
rs.AddNew
rs!test_field = "21,345"
rs.Update
rs.Close
Set rs = Nothing
End Sub
TestVBA_OtherBase 数据库中的结果是21,345
【问题讨论】:
从列表中看不出来,但 TableAdo 是使用默认 EnableBCD 值 (True) 创建的。 没有问题,@J...,现在添加输出。you get actual value multiplied with scale, ie: you set 12,354 you get 12354
我不认为这是一个乘法。这很可能是 ADO 将逗号解释为千位分隔符。
在 ADO 案例中,DB 中的实际值是多少?它是否正确存储了12,345
并且在检索值时出错了?还是它实际上存储了12345
?
@J... 对,如果您将 DB 字段创建为 Decimal(X,Y) 您最终会在数据库中得到 Value * 10^Y。我的意思是,如果您只是发布“1”,就我而言,您将获得 1000。所以回答你的第二个问题:数据库存储 12345。
【参考方案1】:
不是一个实际的答案,但我最终切换回 MS Access 中的浮点字段。 另一种解决方案可能是从 ADO 切换到 FireDAC。仍有希望,Embarcadero 有一天会帮助解决这个问题,请注意 RSP-34075 的进展。
【讨论】:
以上是关于Delphi ADO BCD 十进制字段值设置不正确的主要内容,如果未能解决你的问题,请参考以下文章