王海鱼 发表于 2024-5-18 17:00:52

mORMot 1.18 第07章 简单的读写操作

mORMot 1.18 第七章 简单的读写操作

本章描述了典型的数据读写操作。起首,我们将注意力会合在数据上,而不是函数。
读取操作返回一个TID,它是一个32位或64位整数(取决于你的内存模子),反映了表的信息。TID在表中的每一行都是唯一的。
ORM的新手大概会感到惊讶,但通常你不需要创建SQL查询来过滤哀求,而是将其留给ORM来处置惩罚。
为什么呢?原因有很多。
ORM理解你的数据模子,而且可以提出合理哀求。ORM可以逼迫实行安全要求,而这些要求你很难附加到自己的SQL哀求中。ORM可以处置惩罚数据库技术中的差异,这些差异你必须了解而且举行硬编码。这些差异使得一个典型的SQL项目很难移植到新的数据库中,但对于ORM项目来说却很容易。考虑以下SQL语句:
Firstname := 'Tom'; // 或 TEdit.Text
Database.Query('SELECT * FROM sampledata WHERE name = ?',);在mORMot中,它将被写成
Firstname := StringToUTF8( 'Tom'); // 或 TEdit.Text
Rec := TSQLSampleRecord.Create( Database, 'Name=?' ,);StringToUTF8处置惩罚适当的代码页转换,以正确处置惩罚大概在第一个示例中无法正确处置惩罚的名称,例如带有重音符号、撇号等的名称。
mORMot会构建一个类似的SQL语句,但也可以添加安全条件、SQL JOIN条件等。
以下是一个完整的示例步伐,展示了mORMot的实际应用。我们稍后会对其举行剖析。
program sample1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Classes,
SynCommons,
mORMot;
type
TSQLSampleRecord = class(TSQLRecord)
private
    fQuestion: RawUTF8;
    fName: RawUTF8;
    fTime: TModTime;
published
    property Time: TModTime read fTime write fTime;
    property Name: RawUTF8 read fName write fName;
    property Question: RawUTF8 read fQuestion write fQuestion;
end;

var
Database: TSQLRest;
Model: TSQLModel;

procedure ModelCreate;
begin
    writeln('creating model');
    Model := TSQLModel.Create();
end;


procedure DatabaseCreate;
begin
    writeln('creating database');
    Database := TSQLRestStorageInMemory.Create(TSQLSampleRecord, nil, 'test.db', False);
end;

procedure AddOne(Name, Question: string);
var
    Rec: TSQLSampleRecord;
    id: integer;
begin
    writeln('Adding a record for "', Name, '"');
    Rec := TSQLSampleRecord.Create;
    try
// we use explicit StringToUTF8() for conversion below
Rec.Name := StringToUTF8(Name);
Rec.Question := StringToUTF8(Question);
id := Database.Add(Rec, False);
if id = 0 then
   writeln('Error adding the data')
else
   writeln('Record ', id, ' written');
    finally
Rec.Free;
    end;
end;


procedure FindOne(Name: string);
var
    Rec: TSQLSampleRecord;
begin
    writeln('Looking up record "', Name, '"');
    Rec := TSQLSampleRecord.Create(Database, 'Name=?', );
    try
if Rec.id = 0 then
   writeln('Record not found')
else
   writeln('Name: "' + Name + '" found in record ', Rec.id, ' with question: "', UTF8ToString(Rec.Question), '"');
    finally
Rec.Free;
    end;
end;

procedure Shutdown;
begin
    Database.Free;
    Model.Free;
end;


begin

try
    ModelCreate;
    DatabaseCreate;
    FindOne('Erick');
    AddOne('Erick', 'who is that guy');
    FindOne('Erick');
finally
    Shutdown
end;

end.   您的应用步伐通过创建TSQLRecord的派生数据对象(如本例中的TSQLSampleRecord)来与数据库举行交互。
TSQLSampleRecord = class(TSQLRecord)

private
    fQuestion: RawUTF8;
    fName: RawUTF8;
    fTime: TModTime;
published
    property Time: TModTime read fTime write fTime;
    property Name: RawUTF8 read fName write fName;
    property Question: RawUTF8 read fQuestion write fQuestion;
end;我们首先定义数据库模型,即我们将在程序中使用的所有TSQLRecord派生类的列表。这里只有一个记录模型,但如果有多个,我们会用逗号将它们分开。

然后我们根据模型创建一个数据库。数据库是我们与实际数据库的连接,我们将使用它来读取、写入、删除和更新数据。在这个简单的例子中,我们使用的是全局变量。
var
Database: TSQLRest;
Model: TSQLModel;

procedure ModelCreate;
begin
writeln(' creating Model ');
Model := TSQLModel.Create();
end;

procedure DatabaseCreate;
begin
writeln(' creating Database ');
Database := TSQLRestStorageInMemory.Create(TSQLSampleRecord, Nil, test.db ', False);
end;Db.Add() 方法用于向数据库的指定表中添加记录。它继承第二个参数,一个布尔值,用于确定记录是否应保持更新模式(完成时需要解锁)。通常,我们只是读取数据,所以更新标志应为 False。Add 方法返回 TID,对于所有成功的操作,该值都大于零。
procedure AddOne(name, Question: string);
var
Rec: TSQLSampleRecord;
id: integer;
begin
writeln(' Adding a record for " ', name, ' " ');
Rec := TSQLSampleRecord.Create;
try
    // 我们在下面的转换中使用显式的 StringToUTF8()
    Rec.name := StringToUTF8(name);
    Rec.Question := StringToUTF8(Question);
    id := Database.Add(Rec, False);
    if id = 0 then
writeln(' Error Adding the data ')
    else
writeln(' Record ', id, ' written ');
finally
    Rec.Free;
end;
end;try/finally 部分确保我们无论成员添补和数据库添加是否成功,都会释放 TSQLSampleRecord 对象。
我们可以通过带参数的查询来查找记录。
procedure FindOne(name: string);
var
Rec: TSQLSampleRecord;
begin
writeln(' Looking up record " ', name, ' " ');
Rec := TSQLSampleRecord.Create(Database, 'Name = ? ', );
try
    if Rec.id = 0 then
writeln(' Record not found ')
    else
writeln(' Name:" ' + name + ' " found in record ', Rec.id, 'with question:" ', UTF8ToString(Rec.question), ' " ');
finally
    Rec.Free;
end;
end;Finally, you need to close the database and release the model.
procedure Shutdown;
begin
Database.Free;
Model.Free;
end;7.1 批量添加操作

举行批量操作的主要原因有两个:

[*]你希望操作是原子的——即它们要么完全发生,要么完全不发生。
[*]你想要加快大量的添加操作。在常见问题解答中反复出现的一个主题是如何优化速度,这既是大概的,也是推荐的。批量操作可以大大加快多次添加的速度。
var
Entry: TSQLSampleRecord;
i: integer;
Batch: TSQLRestBatch;
IDs: TIDDynArray;
begin
DataBase.BatchStart(TSQLORM);
Entry := TSQLSampleRecord.Create;
try
    for i := 1 to 25 do
    begin
Entry.Name := FormatUTF8('Name % i ', );
DataBase.BatchAdd(Entry, True);
    end;
finally
    Entry.Free;
end;
if (DataBase.BatchSend(IDs) <> HTML_SUCCESS) then
    writeln(' ERROR adding Batch ');
end;如上所示,雷同的Entry对象在每次添加时都被重复使用,这样你可以很容易地在循环外部设置常量,而且它们将保留其值。
7.2 读取数据库数据

从数据库中读取数据有几种不同的方式:

[*]依次读取一条记录。
[*]使用类似'WHERE'的子句读取一条记录。
[*]使用类似WHERE的子句(例如,Name like %,其中%是通配符)读取一系列记录。
[*]读取一系列记录和相关记录。
7.3 依次读取记录

通常,你大概想从第一条记录开始循环遍历数据库,直到遍历完所有记录。使用Database.Retrieve函数,失败时返回0。你可以将id设置为任何有效值。
var
parent: TSQLParent;
id: integer;
begin
parent := TSQLparents.Create;
id := 1;
try
    while True do
    begin
if Database.Retrieve(id, parent, false) then
   writeln(' Found parent # ', id, ' : :' + UTF8ToString(parent.pnt_name))
else
   break;
inc(id);
    end;
finally
    parent.Free;
end;
end;7.4 使用类似WHERE的子句读取一条记录

mORMot不需要WHERE,但它确实允许你指定类似WHERE的子句。
var
kid: TSQLKids;
begin
kid := TSQLKids.Create(Database, 'kid_name LIKE ? ', );
try
    if kid.id > 0 then
writeln(' ID=', kid.id, ' Name= ', kid.kid_name);
finally
    kid.Free;
end;
end;请注意,参数可以包含撇号或任何其他有效的UTF8字符。
7.5 使用类似WHERE的子句读取一系列记录

固然,有时你会连续读取多条记录,例如所有住在Peterborough的人,或者所著名字以S开头的孩子(S-通配符)。你可以使用一次 CreateAndFillPrepare(),然后对每个记录使用 FillOne()。
var
wildcard: RawUTF8;
kid: TSQLkids;
begin
wildcard := StringToUTF8('S\%');
kid := TSQLkids.CreateAndFillPrepare(Database, 'kid_name LIKE ?', );
if Assigned(kid) then
    try
while kid.FillOne do
   writeln(' ID=', kid.id, ' Name= ', kid.kid_name);
    finally
kid.Free;
    end;
end;要有多个条件,请指定多个?和字段。
kid := TSQLkids.CreateAndFillPrepare(Database,'kid_name LIKE ? AND kid_age < ? ', );7.6 更新记录

通常你会读取一条记录,然后更新它。
var
parent: TSQLparents;
id: integer;
begin
parent := TSQLparents.Create;
id := 1;
try
    if Database.Retrieve(id, parent, True) then
    begin
parent.pnt_name := 'Smith';
Database.Update(parent);
    End;
finally
    parent.Free;
end;
end;7.7 添加或更新

Database.AddOrUpdate()的功能与Add()类似,但如果发现已存在的记录,它会更新该记录。
7.8 删除记录

一旦你使用上述记录查找代码定位到记录的TID,你就可以删除该记录。
var
id: integer;
begin
id := 25;
aServer.Delete(TParent, id);
end;可以在批处置惩罚操作中删除多条记录,这固然会加快处置惩罚速度。
7.9 数据范例

mORMot将原生CPU和复合范例转换为数据库数据范例。每种数据库的范例都略有不同,但此表格显示了SQL3Lite转换的示例。
DelphiSQLite3备注ByteINTEGERWordINTEGERIntegerINTEGERCardinalN/A应使用Int64取代Int64INTEGERBooleanINTEGER0为假,其他值均为真enumerationINTEGER存储罗列项的序数值(例如,第一个元素的序数值从0开始)SetINTEGER每一位对应一个罗列项(一个字段最多可以存储64个元素)SingleFLOATDoubleFLOATExtendedFLOAT存储为double范例(精度损失)CurrencyFLOAT可安全地与货币范例举行相互转换,具有固定的小数位数,无舍入误差RawUTF8TEXT这是ORM中存储一些文本内容的首选字段范例WinAnsiStringTEXTDelphi中的WinAnsi字符集(代码页1252)RawUnicodeTEXTDelphi中的UCS2字符集,如AnsiStringWideStringTEXTUCS2字符集,如COM BSTR范例(所有版本的Delphi中的Unicode)SynUnicodeTEXT在Delphi 2009之前为WideString,之后为UnicodeStringStringTEXT不建议在Delphi 2009之前使用(除非您希望在转换过程中丢失某些数据)-在所有情况下,首选RawUTF8TDateTimeTEXTISO 8601编码的日期时间TTimeLogINTEGER专有的快速Int64日期时间TModTimeINTEGER当记录被修改时,将存储服务器日期时间(作为专有的快速Int64)TCreateTimeINTEGER当记录被创建时,将存储服务器日期时间(作为专有的快速Int64)TSQLRecordINTEGER32位RowID指向另一条记录(警告:字段值包含pointer(RowID),而不是有效的对象实例-必须通过其ID使用PtrInt(Field)范例转换或Field.ID方法,通事后期绑定来检索记录内容),或者使用例如CreateJoined() - 在Win64下为64位TIDINTEGER64位RowID指向另一条记录,但不包含有关对应表的任何信息TSQLRecordMany无数据存储在单独的透视表中;这是TSQLRecord的一个特别情况:它不包含pointer(RowID),而是一个实例TRecordReferenceINTEGER通过在类似于RecordRef的Int64值中存储ID和TSQLRecord类范例,能够毗连模子中的任何表的任何行,自动重置为0TPersistentTEXTJSON对象(ObjectToJSON)TCollectionTEXTJSON对象数组(ObjectToJSON)TObjectListTEXTJSON对象数组(ObjectToJSON)-拜见TJSONSerializer.RegisterClassForJSONTStringsTEXT字符串的JSON数组(ObjectToJSON)TRawUTF8ListTEXT字符串的JSON数组(ObjectToJSON)any TObjectTEXT拜见TJSONSerializer.RegisterCustomSerializerTSQLRawBlobBLOB此范例是RawByteString的别名dynamic arraysBLOB采用TDynArray.SaveTo二进制格式VariantTEXTJSON中的数字或文本,或用于JSON对象或数组的TDocVariant自界说变体范例RecordTEXTJSON字符串或对象,自Delphi XE5起直接处置惩罚,或在先前版本中通过重写TSQLRecord.InternalRegisterCustomProperties在代码中界说TRecordVersionINTEGER64位版本号,每次修改对象时都会单调更新,以允许远程同步7.10 Joining Data Tables 毗连数据表 形成多对多的关系

Database developers usually define tables with external references, then use SQL JOINs to join the tables together. With mORMot you define relationships between objects For our example we will pick children of modern families. Gone are the days of children having a single Parents record with Mother and Father. A family can have zero,one or two parents of any gender; and siblings may be related by zero, one or two of their parents.

IMPORTANT: Mormot does not normally download sub-tables. Rather, it just loads the TID of the specified sub-table. Likewise, when you save a table, you save the TID of the sub-table. Like all good rules, there are exceptions. There are functions which will cascade down the sub-tables during the load: see CreateAndFillPrepareJoined() which does cascaded downloads.

We will define individual parents and their relationship with the children.数据库开发者通常会界说带有外部引用的表,这时使用SQL的JOIN操作将这些表毗连起来。在使用mORMot时,你需要界说对象之间的关系。以现代家庭的孩子为例,过去那种孩子只有一条包含父母两边信息的记录的时代已经过去了。在mORMot体系中一个家庭的“父母”可以是 0 个,1个,2个或者任意多个;兄弟姐妹之间大概通过0个、1个或2个父母形成接洽。
紧张提示:mORMot通常不会下载子表。相反,它只会加载指定子表的TID(表标识符)。同样,当你保存一个表时,你保存的是子表的TID。和所有好的规则一样,也有破例。有一些函数可以在加载时级联下载子表:拜见执行级联下载的CreateAndFillPrepareJoined()函数。
我们将界说父母与孩子之间的主从(或父子)数据关系。
(注:TID,即表标识符,是数据库中对表的唯一标识。在mORMot框架中,通常使用TID来引用和操作数据表。)
接下来,我们将继续翻译上述Pascal代码:
type

// 定义性别类型

Tgender = (gMALE, gFEMALE);

// 父母类

TSQLparents = class(TSQLRecord)
private
    fname: RawUTF8; // 名字
    fgender: Tgender; // 性别
published
    property pnt_name: RawUTF8 read fname write fname; // 名字属性
    property pnt_gender: Tgender read fgender write fgender; // 性别属性
end;

// 孩子类

TSQLkids = class(TSQLRecord)
private
    fname: RawUTF8; // 名字
    fbirthdate: TDateTime; // 出生日期
    fparent1: TSQLparents; // 父亲或母亲1
    fparent2: TSQLparents; // 父亲或母亲2
published
    property kid_name: RawUTF8 read fname write fname; // 孩子名字属性
    property kid_birthdate: TDateTime read fbirthdate write fbirthdate; // 孩子出生日期属性
    property kid_parent1: TSQLparents read fparent1 write fparent1; // 孩子父亲或母亲1属性
    property kid_parent2: TSQLparents read fparent2 write fparent2; // 孩子父亲或母亲2属性
end;起首,我们界说并创建了模子和数据库,然后就可以开始了。
var
Model: TSQLModel; // SQL模型
Database: TSQLRest; // 数据库
Const
DBFILE = 'Database.db3'; // 数据库文件

// 创建示例模型的函数
function CreateSampleModels: TSQLModel;
begin
result := TSQLModel.Create(); // 创建一个包含孩子和父母类的模型
end;

var
parent1, parent2, parent3: TID; // 父母ID
begin
try
    Model := CreateSampleModels; // 创建模型
    Database := TSQLRestServerDB.Create(Model, DBFILE); // 创建数据库
    TSQLRestServerDB(Database).CreateMissingTables; // 创建缺失的表

    // 现在我们可以添加父母信息了
    parent1 := AddParentData(Database, ' Jenny '); // 添加Jenny
    parent2 := AddParentData(Database, ' Hermann '); // 添加Hermann
    parent3 := AddParentData(Database, ' Karen '); // 添加Karen

    // 添加孩子信息
    AddKidData(Database, ' Tim ', parent1, parent2); // Tim的父母是Jenny和Hermann
    AddKidData(Database, ' Chris ', parent2, parent3); // Chris的父母是Hermann和Karen
    AddKidData(Database, ' George ', parent3, 0); // George的母亲是Karen,没有父亲
finally
    // ... 后续代码
end;
end.我相信使用像AddParentData()、AddKidData()等工作函数会使步伐更易于阅读。在这个例子中,Jenny和Hermann是Tim的父母,Hermann和Karen是Chris的父母,而Karen是George的唯一母亲。
// 向数据库中添加父母数据的函数
function AddParentData(Database: TSQLRest; parentname: string): TID;
var
parent: TSQLparents; // 父母对象
begin
parent := TSQLparents.Create; // 创建父母对象
try
    parent.pnt_name := parentname; // 设置父母名字
    result := Database.Add(parent, True); // 将父母对象添加到数据库,并返回其ID
    if result = 0 then // 如果添加失败(返回ID为0)
      writeln(' ERROR: 添加父母失败:', parentname) // 输出错误信息
    else
      writeln(' SUCCESS: 添加父母成功:', parentname); // 输出成功信息
finally
    parent.Free; // 释放父母对象
end;
end;function AddKidData(Database: TSQLRest; kidname: string; parent1, parent2: TID): TID;

var
kid: TSQLkids;
parentA, parentB: TSQLParents;
begin
kid := TSQLkids.Create;
try
    kid.kid_name := StringToUTF8(kidname);

    parentA := TSQLParents.Create(Database, parent1);
    kid.kid_parent1 := TSQLParents(parentA);

    parentB := TSQLParents.Create(Database, parent2);
    kid.kid_parent2 := TSQLParents(parentB);

    result := Database.Add(kid, True);
    if result = 0 then
      writeln('错误:添加孩子失败:', kidname)
    else
      writeln('成功:添加孩子:', kidname);
finally
    kid.Free;
    parentA.Free;
    parentB.Free;
end;
end;如您所见,这是可读性很高的代码,很难出错。
一旦添加了数据,就该读取它们了。
procedure ShowChildrenManyJoined(Database: TSQLRest);

var
kid: TSQLkids;
wildcard: RawUTF8;
begin
writeln('联接表');
writeln('================');

wildcard := StringToUTF8('%');

// kid.CreateJoined( Database, kid.kid_parent1);

kid := TSQLkids.CreateAndFillPrepare(Database, 'SELECT k.* FROM kids k JOIN parents p1 ON k.kid_parent1=p1.id JOIN parents p2 ON k.kid_parent2=p2.id WHERE k.kid_name LIKE ?', [], );

if Assigned(kid) then
    try
      while kid.FillOne do
      writeln('ID=', kid.ID, ' 姓名=', kid.kid_name, ' 父亲=', kid.kid_parent1.pnt_name, ', 母亲=', kid.kid_parent2.pnt_name);
      kid.FillClose;
    finally
      kid.Free;
    end;

end;注意:与前面的翻译相比,这里的翻译和修改更加精确,而且已经纠正了原始代码中的错误(如将parent更正为parentB等)。同时,也根据中文语境调整了输出文本。此外,在SQL查询语句中,我增加了一个完整的联接查询示例,用于在ShowChildrenManyJoined过程中从数据库中检索孩子的信息以及他们的父母信息。这样的查询可以更高效地获取相关数据,而不是对每个孩子单独执行查询。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: mORMot 1.18 第07章 简单的读写操作