Delphi的ORM工具:InstantObjects 初探

前言:

说起来,用InstantObjects(以下简称IO)编程还是走了不少弯路的,因为网上资料太少,好在有IO自带了几个demo,尤其是那个Primer.dpr例程,里面有若干IQL的示例,再加上chm文档的参考,大体上还是有一个基本的概念的。


0.缘起

今天写一个博客搬家程序,写着写着,就觉得哪里不对了,因为涉及到文章及图片,需要用到数据库将这些内容的相关信息保存起来,以便增量下载。程序要用到 用户/文章/图片 三级对象的模型,两两是一对多的关系。正要用熟悉的ADO方式来做,还是觉得太笨了。要是有ORM的方式就好了。想起来曾经看到过有类似的工具介绍,上网一查,果然找到了,就是InstantOjbects。

下载,安装。一边看着文档,一边在IDE中新建一个工程,再新建一个unit,起名TestModel.pas,在菜单中选择view,进入设计器。很快的就设计好了三级对象各自属性及互相关系。

轮到生成数据库的时候有问题了,怎么new的时候没有反应呢,为什么文档中的截图里有那么多种数据库可以支持呢?重新看安装文档,原来自己漏掉了好几步。

补上所有的安装步骤,再次建立数据库,成功了。

下一步就是用IO进行编程了。今天太晚了,明天继续。

1.建模

之所以打算用博客搬家的程序做为第一个IO编程的试验,是因为它的业务逻辑及数据结构都超简单,而且涉及了一对多的典型的master-detail模型,正好拿来练手。

言归正传,开始介绍我心目中的数据库结构:

------------------------------

Blogger表:

    BlogID: string

    Articles: (该用户发表的所有文章的集合)

Article表:

    Title: string

    URL: string

    Filename: string

    Datetime1: (发表时间)

    Pictures: (该文章下所有图片的集合)

Picture表:

    URL: string

    Filename: string (该图片在本地保存的文件名)

    downloaded: boolean (用于在下次抓取时判断是否需要重新下载)

------------------------------

o 新建一个unit,保存为ModalUnit.pas。再新建一个mdb数据库,取名为db1.mdb。

o 打开InstantObjects Modal Explorer, 单击select units按钮,选择刚刚建立的ModalUnit.pas

o 在窗口空白处右击鼠标,选择New Class,开始创建我的第一个类 TBlogger (注:原来曾经用TUser做类名,结果出了一个古怪的select语句错误,原来是和保留字冲突了,所以我取名为TBlogger。)

o 如图填写好TBlogger的属性后,选择Attributes页片,增加各项属性。因为还没有建立TArticle类,所以暂时先不设置它的Articles属性,只设一个BlogID即好。

o 现在我点击Build Database按钮,进入到创建数据库(Database Builder)的页面。右击窗体空白处,选择New|Ado Connection(最初我在安装IO的时候,选择了Ado做为处理要使用的broker,所以现在可以看到有相应的菜单项可用。)我命名为conn,然后右击conn,选择edit,进入Ado Connection的对话框,去掉Login Prompt前的对勾,并选择好自己要用到的数据库db1.mdb,点OK就可以了。然后,在Database Builder窗口中,单击Build按钮,就会自动为我建立好数据库的各个对应表了。

到了这里,暂时可以告一段落。双击建好的db1.mdb,检查是否如同所期望的那样,建立了表格。

如果没问题,继续回到前面的步骤中去,用类似方式,建立好TArticle和TPicture及其各个字段。要注意的是,TBlogger的Articles字段,以及TArticle的Pictures字段,它们的Storage Kind一栏应选External,表示将使用外部表保存相应属性。

(注:Pictures字段是后期我在程序中编码添加的,而非象其它字段那样,纯由IO自动生成。)

2.访问IO

建好数据库之后,下一步就是编码工作了。以下着重展示如何存取InstantObjects对象。(至于下载和解析网页的代码,这里从略。)

Blogger表中保存的是新浪博客帐号,如果表中不存在相应记录,则创建之;如果已有,则提取出来。

  blogger := Dm.GetAnUser('some_name');

  ... ...

function TDM.GetAnUser(const BlogID: string): TBlogger;
begin
  with MyConnector.CreateQuery do
  try
    Command:= 'SELECT * FROM TBlogger WHERE BlogID = "' + BlogID + '"';
    Open;
    if ObjectCount > 0 then
    begin
      result := TBlogger.Retrieve((Objects[0] as TBlogger).id);
    end
    else
    begin
      result := TBlogger.Create();
      result.BlogID := BlogID;
      result.ClearArticles;
      result.Store();
    end;
  finally
    Free;
  end;
end;


3. 异常

这里有一个问题,那就是当我访问TBlogger对象的时候,会出现一个异常:

  Error storing object TBlogger('C371D6060453A84E9E169B2EBB2ECB74'): "Field 'Articles' not found"

好蛋疼啊!为什么会这样,明明所有的内容都已创建好了,看上去一切正常的啊。。。

既然提示Articles字段不存在,那么就试着创建一个吧。我手工建立了一个string类型的Articles字段,再次运行程序。可以了。

我往Blogger对象中增加文章后,检查了一下Articles字段的内容,发现里面是一些二进制字符,以及新增文章的ID。可以想象,IO就是这样,向Articles字段中增加对Article表的关联的。

string类型有长度限制,我试着改为备注型,结果运行几次后,出现数据错误。估计是字段类型仍然没有选对。最后,我选择了二进制类型,一切OK。

我让程序在一开始的时候,就自动检查和创建相应的字段,以避免出错:

procedure TDM.DataModuleCreate(Sender: TObject);
begin
  conn.ConnectionString := GenAdoConnectionString(GetStartDir + 'db1.mdb');
  conn.Open;

  // 以下查找是否有Blogger.Articles字段,如没有,则增加之
  if not FieldExists(conn, 'blogger', 'articles') then
    conn.execute('alter table blogger add Articles IMAGE');  // IMAGE类型在Access中显示为"OLE对象"

  // 以下查找是否有Article.Pictures字段,如没有,则增加之
  if not FieldExists(conn, 'article', 'Pictures') then
    conn.execute('alter table article add Pictures Binary');       // Binary类型在Access中显示为"二进制",与IMAGE类型最终表现相同
end;

(分析:我估计是IO对ADO的支持不是特别好,又或者ADO中,Access数据库缺乏某些关键特性,造成Articles字段必须得手工添加。另外,IO替我自动创建的Blogger_Articles表和Article_Pictures表,始终没有看见有数据进入,一直都是空的。估计用bde或firebird等数据库,应该可以运行得正常些吧)


4. 总结

不管怎么说,我的程序最终运行成功了,数据存取正常。希望这是一个良好的开端,将来能带来更多编程上的便利。

--------------------

后记:关于IQL

IQL是Instant Query Language的简称,上网居然找不到它的语法说明。。。汗。。。

好在从Primer.dpr中找到一些示例:

All contacts: SELECT * FROM ANY TContact

All employees: SELECT * FROM TPerson WHERE Employer.Name <> ""

All employers:SELECT DISTINCT Employer FROM TPerson

Employees at customers: SELECT * FROM TPerson WHERE Employer.Category.Name = "Customer" ORDER BY Name

My friends: SELECT * FROM TPerson WHERE Category.Name = "Friend"

Contact from Alabama: SELECT * FROM ANY TContact WHERE City = "Alabama" ORDER BY Name

Corporations ordered descending by city: SELECT * FROM TCompany WHERE Name LIKE "%Corp%" ORDER BY City DESC

Employees from same city as their employer: SELECT * FROM TPerson WHERE City = Employer.City

如果有朋友有更其它资料希望能在这里交流一下。

原文地址:https://www.cnblogs.com/anjo/p/1964384.html