[AX]AX2012 AIF(二):文档服务编程模型

一个完整的文档服务包含很多对象,以Customer服务为例,它包含以下对象:

  • 查询AxdCustomer:这个query的顶层表为CustTable,其下Datasource包含表DirParty,DirParty下的Datasource则再包含表DirPersonName、表DirOrganizationName、视图DirPartyContactInfoView、视图DirPartyPostalAddressView。这个Query定义了服务所用的所有相关数据的数据模型。
  • 文档服务类CustCustomerService:这是直接提供服务的类,它包含Create、Delete、find、findKeys、getKeys、getChangedKeys等对外服务的方法,这些方法使用特定的特性标注,比如Create方法使用了[AifDocumentCreateAttribute, SysEntryPointAttribute(true)]标注,AifDocumentCreateAttribute指出这个方法是文档服务的创建方法,这个特性不是必须的,主要用途是在使用metadata服务时我们可以根据这个特性列举出所有删除记录的服务操作;SysEntryPointAttribute特性指示是否进行授权检查,所有的服务操作必须指定这个特性,其参数true表示要对调用用户检查是否有权操作该方法涉及到的数据表,false则不执行这个检查。文档服务类服务的具体操作都交由其继承的基类AifDocumentService具体操作,比如read()方法内部调用的是AifDocumentService.readList()方法。
  • 数据对象类CustCustomer、CustCustomer_CustTable、CustCustomer_DirParty、CustCustomer_DirParty_DirPerson等:这些类描述了查询AxdCustomer定义的数据模型,CustCustomer直接对应Query AxdCustomer,它是数据对象类的最顶层,它依靠其他数据类CustCustomer_CustTable、CustCustomer_DirParty等。CustCustomer从AifDocument继承,后者又从AfStronglyTypedDataContainer继承,而AfStronglyTypedDataContainer又从AifXmlSerializable继承,由它描述了XML消息定义的一个文档,所以我们在read()服务操作方法中看到的返回值就是这个类的一个实例。其他的数据对象类都直接从AfStronglyTypedDataContainer继承,所以数据对象类包括CustCustomer都实现一系列的existsXXX()方法,这些方法判断某个字段是否存在,调用基类的exists()方法;一系列parmXXX()方法,获取某个字段的值或者下层的数据对象实例;createXXX()方法,创建并返回其下层的数据对象类实例列表,比如CustCustomer.createCustTable()返回包含CustCustomer_CustTable类实例的列表。
  • 文档类AxdCustomer:文档类的作用是封装涉及到的多个表业务逻辑,这样外部调用应用不需要确切的知道如何操作底层数据库表。Axd类实现AifServiceable接口,这里的AxdCustomer不是直接实现这个接口,而是从AxBase扩展,后者实现AifServiceable接口。总体上来讲Axd类实现到XML的序列化及反序列化,生成XSD数据Schema,控制内部表的生命周期等,由它间接的操作下面要讲到的AxXXX表类。Axd类包含的方法很多,更详细的介绍可以参见http://msdn.microsoft.com/EN-US/library/aa862063.aspx,这里列举几个实现接口AifServiceable比较重要的方法:getName()返回文档的名称,这是XML文档的根标签的名称;getSchema()返回数据Schema XSD;getActionList()返回文档支持的操作列表,比如AxdCustomer支持findList、read、readList等操作。
  • 表类AxCustTable、AxDirPartyTable等:这组类以Ax<Table>方式命名,和文档类协同工作,同样封装数据表业务逻辑,从AxInternalBase继承,代表了AOT中的某个表。Ax<Table>类不是必须的,在使用AIF Document Wizard创建新的文档服务时,勾选了“Generate AxBC Classes”才会创建这些类。如果使用Ax<Table>类,可以在文档级别使用“Value mapping” form来映射数据表字段。Ax<Table>内部使用类AxdBaseRead、AxdBaseCreate读写数据库表,不使用Ax<Table>的情况则可以在Axd文档类中使用AxCommon读写数据库表。需要注意的如果在服务的Query中添加了一个新的表,相应的Ax<Table>类不会自动生成,这时候可以使用Update document service”工具的“Regenerate data object classes”和“Update AxBC classes”选项重新生成或者更新Ax<Table>类。Ax<Table>类和AOT中的表是一一对应的,由它直接操作表数据,具体功能包括生成表字段的默认值、按照正确的顺序设置表字段值、维护验证关联表数据的完整性、字段值映射比如从供应商料名映射到内部料名、错误处理等,需要注意的是Ax<Table>不会验证是谁在操作数据表,用户验证要放到前面提到的服务类。

看完相关的类和对象,在开始后续的问题前,我们先来看看Schema XSD是如何生成的。数据对象类继承自AifDocument(顶级对象),也可能是AfStronglyTypedDataContainer,AfStronglyTypedDataContainer有一个方法叫做getSchema()返回Schema XSD;而AifDocument是继承自AfStronglyTypedDataContainer,它重载了getSchema()方法,它会创建对应Axd类的实例,调用前面提及的Axd类的getSchema()返回Schema XSD。实际上无论是AifDocument.getSchema()还是AfStronglyTypedDataContainer.getSchema(),它们最终都使用AxdBaseGenerateXSD.generate()生成Schema XSD。这里不深入讨论是如何生成XSD的,需要知道的是以上方法会枚举文档定义的Query,从Query中查找Datasource字段生成相应的XML标记,文档Query必须只有一个根Datasource,隐藏或者禁止的字段被排除在外,XML的根元素名称来自于Axd<document>去掉Axd前缀。我们可以用下面的Job从代码生成相应文档服务的XSD:

static void GenerateXSDSchema_Customer(Args _args)
{
    CustCustomer        customer;
    XML                 xml;
    XMLDocument         xmlDocument;
    FileName            fileName;
    ;

    // Instantiate the class.
    customer = new CustCustomer();

    // Get the document class schema.
    xml =customer.getSchema();
    xmlDocument = XMLDocument::newXML(xml);

    // Save the schema to a file.
    fileName = "c:\\XSDSchema_Customer.xsd";

    new FileIoPermission(fileName, 'rw').assert();
    xmlDocument.save(fileName);
    CodeAccessPermission::revertAssert();
}

在生成的Schema中我们可以看到AxdCustomer类被映射为complexType类型,其下包含的元素是从AxdCustomer的parmXXX方法去掉parm而来;Query中的表Custtable也映射为complexType类型,名称为AxdEntity_CustTable,包含的元素来自于表字段,只有那些包含在AxCustTable.parmXXX方法的字段才会出现在XSD中。更详尽的字段类型到XSD的单元的映射关系参见http://msdn.microsoft.com/EN-US/library/aa636469.aspx。要说明的是上面得到的文档服务的完整XSD,而我们在端口配置窗口中“View schema”看到的XSD是完整XSD的子集,在“Document data policies”窗口我们可以手工使能或者禁止某个字段,这只是对当期所配置的AIF端口有效。

XSD描述了XML消息的格式,下面是CustCustomerService.read操作得到的XML序列化结果样例(省略部分内容):

  <?xml version="1.0" encoding="UTF-8" ?> 
- <Envelope xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
- <Header>
   <MessageId>{93FE7B5F-99E6-45D6-BAA5-654699EFF0EA}</MessageId> 
   <Action>http://schemas.microsoft.com/dynamics/2008/01/services/CustomerService/read</Action> 
   <RequestMessageId>{E983D78F-0011-47B7-8716-F8B64D120EF6}</RequestMessageId> 
  </Header>
- <Body>
  - <MessageParts xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
  - <Customer xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/Customer">
      <DocPurpose>Original</DocPurpose> 
      <SenderId>DMO</SenderId> 
      <ValidAsOfDateTime>2012-04-19T19:42:40Z</ValidAsOfDateTime> 
      <ValidTimeStateType>AsOf</ValidTimeStateType> 
    - <CustTable class="entity">
        <_DocumentHash>261dcc95694f19ee9010b1866237b4a2</_DocumentHash> 
        <AccountNum>4503</AccountNum> 
        <AccountStatement>Always</AccountStatement> 
        <Blocked>No</Blocked> 
        <CashDisc>14D1%</CashDisc> 
 ......
        
        <WebSalesOrderDisplay>WebEntered</WebSalesOrderDisplay> 
      - <DirParty xsi:type="AxdEntity_DirParty_DirOrganization" class="entity" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
          <LanguageId>En-us</LanguageId> 
           <Name>3 Company</Name> 
           <NameAlias>3</NameAlias> 
           <PartyNumber>1310</PartyNumber> 
           <RecId>5637145091</RecId> 
           <RecVersion>1</RecVersion> 
         - <DirPartyPostalAddressView class="entity">
             <Address>522 West 5th Street New York, NY 10032 US</Address> 
             <City>New York</City> 
  ....
             <ABC>None</ABC> 
           - <OrganizationName class="entity">
               <Name>3 Company</Name> 
               <RecId>5637144581</RecId> 
               <RecVersion>1</RecVersion> 
               <ValidFrom>2009-06-13T00:17:00Z</ValidFrom> 
               <ValidTo>2154-12-31T23:59:59Z</ValidTo> 
             </OrganizationName>
           </DirParty>
         </CustTable>
       </Customer>
     </MessageParts>
    </Body>
  </Envelope>

消息包括封皮Envelope和Header段,Header段的Action指定操作的名称。有几点需要说明,注意到Axd类parmXXX被序列化到XML;Query中的表加上了class="entity"属性;如果使用了Ax<Table>,只有Ax<Table>.parmXXX方法指定的内容被序列化,并且由它来读取验证数据,否则数据直接从数据库表读出Query中指定的字段。

我们已经知道文档服务的具体操作是在文档服务类中实现,标准的文档服务操作包括create、delete、find、findKeys、read、update、getKeys、getChangedKeys。在AOT的Services节点下我们可以新建一个Service来引用这些操作,进而在出入站端口中使用。当然不是每一个文档都需要实现上述所有的标准服务,此外我们还可以添加自定义的服务操作,这些自定义服务操作方法必须定义为public,如果参数或者返回值是个对象类,那么这个对象类必须实现AifXmlSerializable接口,如果不是对象类则只有以下几种元类型被支持:str、 date、 utcdatetime、 guid、 int、 int64、 enum、real、void。如何创建一个自定义服务可以参见http://msdn.microsoft.com/EN-US/library/aa607052.aspx

在findKeys、update、read等方法中用到类AifEntityKeyList,它表示的是一个键值对,比如我们要读取一个Customer的信息,传入的键值对可能是AccountNum=5407。如果我们在自定义的方法中需要返回大量数据,可以考虑只返回记录的键值对,然后再用read方法根据键值对取出实际的记录数据,这有助于提高性能。

后续还有更多关于文档服务的内容......

posted @ 2013-01-30 14:50  断水流  阅读(1292)  评论(0编辑  收藏  举报