调用 Apex 方法

Lightning Web 组件可以从 Apex 类导入方法。导入的方法是组件可以通过或命令性调用的函数。@wire

在使用 Apex 方法之前,请确保没有更简单的方法来获取数据,例如,使用基本 Lightning 组件。请参阅数据指南。

重要

假设 Apex 方法提供不可变数据,即使今天可以改变数据。最佳做法是将组件接收的所有数据视为不可变数据。若要更改数据,请创建要更改的对象的浅拷贝。在处理数据时,了解这个概念非常重要。请参阅数据流。

lwc-recipes 存储库具有多个调用 Apex 方法的组件。查找名称以 开头的组件。apex

Apex限制

每次调用 Apex 方法时都会应用 Apex 限制。如果您的 JavaScript 代码通过或命令性方式调用多个 Apex 方法,则 Apex 限制将分别应用于每个方法调用。@wire

导入 Apex 方法

使用 JavaScript 中的默认导入语法通过作用域包导入 Apex 方法。@salesforce/apex

语法

import apexMethodName from "@salesforce/apex/namespace.classname.apexMethodReference";
  • apexMethodName– 标识 Apex 方法的符号。
  • apexMethodReference– 要导入的 Apex 方法的名称。
  • classname– Apex 类的名称。
  • namespace– 如果类与组件位于同一命名空间中,则不要指定命名空间。如果该类位于托管包中,请指定托管包的命名空间。

用法

当 Apex 类名称在 JavaScript 源文件之外发生更改时,该类名称会在 JavaScript 源文件中自动更新。JavaScript 源文件中不会更新方法和参数名称更改。

要调用 Apex 方法,Lightning Web 组件可以:

  • 将 Apex 方法连接到 Lightning Web 组件
  • 命令式调用 Apex 方法

向 Lightning Web 组件公开 Apex 方法

要向 Lightning Web 组件公开 Apex 方法,该方法必须是 and 或 。用 注释方法。staticglobalpublic@AuraEnabled

语法

public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != null
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

输入和输出支持这些类型。

  • 基元 – Boolean、Date、DateTime、Decimal、Double、Integer、Long 和 String。
  • sObject – 支持标准和自定义 sObject。
  • Apex – Apex 类的实例。 (最常见的是自定义类。
  • 集合 – 任何其他类型的集合。

注意

  • 不支持将 Apex 内部类作为 Lightning Web 组件调用的 Apex 方法的参数或返回值。
  • 不支持从 Lightning Web 组件引用的 Apex 方法的 Apex 注释。从软件包安装的 Lightning Web 组件无法从另一个软件包中的 Apex 类调用 Apex 方法,即使两个软件包位于同一命名空间中也是如此。@NamespaceAccessible@AuraEnabled

用法

Lightning 组件框架不强制执行任何有关 Apex 类位置的规则。如果您使用的是 Salesforce DX,请将 Apex 类放在目录中。<app dir>/main/default/classes

构造 Apex 方法时,必须决定是将参数作为基元传入,还是作为复杂数据类型(如 Apex 类或 sObject)传入。

提示

lwc-recipes 存储库有一个 apexImperativeMethodWithParams 和 apexImperativeMethodWithComplexParams 组件示例,该示例调用具有基元或复杂参数的方法。

将 Apex 方法连接到 Lightning Web 组件

为了读取 Salesforce 数据,Lightning Web 组件使用反应式连线服务。在组件的 JavaScript 类中用于指定 Apex 方法。可以使用属性或函数来接收数据。要对返回的数据进行操作,需要函数。@wire@wire@wire

若要用于调用 Apex 方法,请使用 批注 Apex 方法。在发出网络调用以调用服务器上的 Apex 方法之前,将检查客户端 Lightning 数据服务缓存。要刷新过时的数据,请调用 ,因为 Lightning 数据服务不管理 Apex 预置的数据。@wire@AuraEnabled(cacheable=true)refreshApex()

使用此语法导入 Apex 方法并将其连接到组件。

import apexMethodName from '@salesforce/apex/namespace.classname.apexMethodReference';
@wire(apexMethodName, { apexMethodParams })
propertyOrFunction;
  • apexMethodName– 标识 Apex 方法的符号。
  • apexMethodReference– 要导入的 Apex 方法的名称。
  • classname– Apex 类的名称。
  • namespace– Salesforce 组织的命名空间。指定命名空间,除非组织使用默认命名空间 (),在这种情况下,请勿指定它。c
  • apexMethodParams– 具有与 的参数匹配的属性(如果需要)的对象。如果参数值为 ,则调用该方法。如果参数值为 ,则不调用该方法。如果 Apex 方法重载,则选择要调用的方法的选择是不确定的(实际上是随机的),并且传递的参数现在或将来可能会导致错误。不要重载 Apex 方法。apexMethodnullundefined@AuraEnabled重要apexMethodParams是一个对象。若要将参数值传递给 Apex 方法,请传递其属性与 Apex 方法的参数匹配的对象。例如,如果 Apex 方法采用字符串参数,则不要直接传递字符串。相反,请传递一个对象,该对象包含值为字符串的属性。命令式和有线 Apex 调用都不支持使用映射。请参阅将值传递给 Apex。
  • propertyOrFunction– 从线路服务接收数据流的私有属性或函数。如果属性被修饰为 ,则结果将返回到该属性的属性或属性。如果函数用 修饰,则结果将在具有属性或属性的对象中返回。@wiredataerror@wiredataerror注意属性和属性是 API 中的硬编码值。您必须使用这些值。dataerror

将 Apex 方法连接到属性

让我们看一下 lwc-recipes 存储库中的组件。此组件打印 Apex 方法返回的联系人列表。apexWireMethodToProperty

为了获取其数据,该组件连接了一个 Apex 方法。Apex 方法进行 SOQL 查询,该查询返回每个联系人都有图片的联系人列表。(此组件不呈现图片,但其他示例组件会呈现。正如我们所了解的,要返回一个简单的联系人列表,最好使用 getListUi。但是要使用 SOQL 查询来选择某些记录,我们必须使用 Apex 方法。

请记住,该方法必须是 、 和 或 。该方法必须用 修饰。staticglobalpublic@AuraEnabled(cacheable=true)

public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != null
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

该组件的 JavaScript 代码导入 Apex 方法,并通过 wire 服务调用它。电汇服务要么设置属性的联系人列表,要么向属性返回错误。contacts.datacontacts.error

// apexWireMethodToProperty.js
import { LightningElement, wire } from "lwc";
import getContactList from "@salesforce/apex/ContactController.getContactList";

export default class ApexWireMethodToProperty extends LightningElement {
  @wire(getContactList) contacts;
}

该模板使用 lwc:if 指令来检查属性是否为真值。如果是,它会循环访问它并呈现每个联系人的名称。如果为 truey,则组件呈现 .contacts.datacontacts.error<c-error-panel>

<!-- apexWireMethodToProperty.html -->
<template>
  <lightning-card title="ApexWireMethodToProperty" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <template lwc:if={contacts.data}>
        <template for:each={contacts.data} for:item="contact">
          <p key={contact.Id}>{contact.Name}</p>
        </template>
      </template>
      <template lwc:elseif={contacts.error}>
        <c-error-panel errors={contacts.error}></c-error-panel>
      </template>
    </div>
  </lightning-card>
</template>

使用动态参数连接 Apex 方法

现在,让我们连接一个接受参数的 Apex 方法。此组件也在 lwc-recipes 存储库中。

Apex 方法采用一个名为 的字符串参数,并返回其名称包含该字符串的联系人列表。searchKey

// ContactController.cls
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> findContacts(String searchKey) {
        String key = '%' + searchKey + '%';
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Name LIKE :key AND Picture__c != null
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

组件的 JavaScript 在参数值前面加上 ,以指示它是动态的和反应式的。它引用组件实例的属性。如果其值发生更改,模板将重新呈现。searchKey$

重要

对于 Apex 方法的组件,第一个参数是一个字符串,它是 Apex 方法的名称,在本例中为 .第二个参数是一个对象,其中包含要传递给 Apex 方法的参数。因此,例如,即使采用字符串,我们也不会将字符串作为第二个参数传递。相反,我们传递一个对象,该对象包含一个属性,其值为字符串: 。(如果 Apex 方法采用另一个参数,我们将向对象添加另一个属性。@wirefindContacts@wirefindContacts@wire{ searchKey: '$searchKey' }

// apexWireMethodWithParams.js
import { LightningElement, wire } from "lwc";
import findContacts from "@salesforce/apex/ContactController.findContacts";

/** The delay used when debouncing event handlers before invoking Apex. */
const DELAY = 300;

export default class ApexWireMethodWithParams extends LightningElement {
  searchKey = "";

  @wire(findContacts, { searchKey: "$searchKey" })
  contacts;

  handleKeyChange(event) {
    // Debouncing this method: Do not update the reactive property as long as this function is
    // being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
    window.clearTimeout(this.delayTimeout);
    const searchKey = event.target.value;
    this.delayTimeout = setTimeout(() => {
      this.searchKey = searchKey;
    }, DELAY);
  }
}

模板用作字段。searchKeyvalue<lightning-input>

<!-- apexWireMethodWithParams.html -->
<template>
  <lightning-card title="ApexWireMethodWithParams" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <lightning-input
        type="search"
        onchange={handleKeyChange}
        class="slds-m-bottom_small"
        label="Search"
        value={searchKey}
      ></lightning-input>
      <template lwc:if={contacts.data}>
        <template for:each={contacts.data} for:item="contact">
          <p key={contact.Id}>{contact.Name}</p>
        </template>
      </template>
      <template lwc:elseif={contacts.error}>
        <c-error-panel errors={contacts.error}></c-error-panel>
      </template>
    </div>
  </lightning-card>
</template>

提示

要观看此示例代码的演示,请参阅 Lightning Web 组件视频库中的将 Apex 方法连接到属性

连接具有复杂参数的 Apex 方法

此示例演示如何调用将对象作为参数的 Apex 方法。任何 Apex 方法的语法都是相同的,但此示例显示了用于生成和传递对象的模式。@wire

该组件有三个输入字段,这些字段采用一个字符串、一个数字和多个列表项,组件使用这些字段来生成列表。Apex 方法只是根据值连接并返回一个字符串。当输入值发生更改时,将调用 Apex 方法并预配新数据。@wire

<!-- apexWireMethodWithComplexParams.html -->
<template>
  <lightning-card title="ApexWireMethodWithComplexParams" icon-name="custom:custom63">
    <div class="slds-var-m-around_medium">
      <lightning-input
        label="String"
        type="string"
        value={stringValue}
        class="string-input"
        onchange={handleStringChange}
      ></lightning-input>
      <lightning-input
        label="Number"
        type="number"
        min="0"
        max="100"
        value={numberValue}
        class="number-input"
        onchange={handleNumberChange}
      ></lightning-input>
      <lightning-input
        label="List items"
        type="number"
        min="0"
        max="10"
        value={listItemValue}
        class="list-item-input"
        onchange={handleListItemChange}
      ></lightning-input>
      <br />
      <template lwc:if={apexResponse.data}>
        <p>{apexResponse.data}</p>
      </template>
    </div>
    <template lwc:elseif={apexResponse.error}>
      <c-error-panel errors={error}></c-error-panel>
    </template>
  </lightning-card>
</template>

Apex 方法采用一个对象 。CustomWrapper

public with sharing class ApexTypesController {
    @AuraEnabled(cacheable=true)
    public static String checkApexTypes(CustomWrapper wrapper) {
        // The values are based on the data that is defined in the
        // apexWireMethodWithComplexParams Lightning web component.
        String response =
            'You entered "' +
            wrapper.someString +
            '" as String, and "' +
            wrapper.someInteger +
            '" as Integer value. The list contained ' +
            wrapper.someList.size() +
            ' items.';
        return response;
    }
}
public with sharing class CustomWrapper {
    @TestVisible
    class InnerWrapper {
        @AuraEnabled
        public Integer someInnerInteger { get; set; }
        @AuraEnabled
        public String someInnerString { get; set; }
    }

    @AuraEnabled
    public Integer someInteger { get; set; }
    @AuraEnabled
    public String someString { get; set; }
    @AuraEnabled
    public List<InnerWrapper> someList { get; set; }
}

若要将参数值从组件传递到 Apex 方法,请使用对象。在此示例中,Apex 方法采用一个对象 ,因此该组件会生成一个匹配的对象以传入 .CustomWrapper@wire

// apexWireMethodWithComplexParams.js
import { LightningElement, wire } from "lwc";
import checkApexTypes from "@salesforce/apex/ApexTypesController.checkApexTypes";

export default class ApexWireMethodWithComplexParams extends LightningElement {
  listItemValue = 0;
  numberValue = 50;
  stringValue = "Some string";

  parameterObject = {
    someString: this.stringValue,
    someInteger: this.numberValue,
    someList: [],
  };

  @wire(checkApexTypes, { wrapper: "$parameterObject" })
  apexResponse;

  handleStringChange(event) {
    this.parameterObject = {
      ...this.parameterObject,
      someString: (this.stringValue = event.target.value),
    };
  }

  handleNumberChange(event) {
    this.parameterObject = {
      ...this.parameterObject,
      someInteger: (this.numberValue = parseInt(event.target.value, 10)),
    };
  }

  handleListItemChange(event) {
    const someList = [];
    for (let i = 0; i < event.target.value; i++) {
      someList.push({
        someInnerString: this.stringValue,
        someInnerInteger: this.numberValue,
      });
    }
    this.parameterObject = {
      ...this.parameterObject,
      someList,
    };
  }
}

将 Apex 方法连接到函数

现在让我们看一下 lwc-recipes 存储库中的组件。此组件将 Apex 方法调用连接到函数。由于结果被预配到函数,因此 JavaScript 可以对结果进行操作。此外,模板访问数据的方式与将结果预配到属性时略有不同。apexWireMethodToFunction

渲染的组件与(标头除外)相同。apexWireMethodToProperty

此组件调用与 相同的 Apex 方法。该方法必须是 、 和 或 。该方法必须用 修饰。apexWireMethodToPropertystaticglobalpublic@AuraEnabled(cacheable=true)

// ContactController.cls
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != null
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

该组件的 JavaScript 代码导入 Apex 方法,并通过 wire 服务调用它。线路服务通过具有 or 属性的对象将结果提供给函数。如果电汇服务提供 ,则将其分配给 ,该模板将用于。如果它提供 ,则将其分配给 ,该模板中也会使用。如果这些属性的值发生更改,模板将重新呈现。wiredContacts()errordatadatathis.contactserrorthis.error

// apexWireMethodToFunction.js
import { LightningElement, wire } from "lwc";
import getContactList from "@salesforce/apex/ContactController.getContactList";

export default class ApexWireMethodToFunction extends LightningElement {
  contacts;
  error;

  @wire(getContactList)
  wiredContacts({ error, data }) {
    if (data) {
      this.contacts = data;
      this.error = undefined;
    } else if (error) {
      this.error = error;
      this.contacts = undefined;
    }
  }
}

该模板使用该指令来检查 JavaScript 属性。如果它存在,它会循环访问它并呈现每个联系人的名称。如果该属性存在,则组件将呈现 .lwc:ifcontactserror<c-error-panel>

<!-- apexWireMethodToFunction.html -->
<template>
  <lightning-card title="ApexWireMethodToFunction" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <template lwc:if={contacts}>
        <template for:each={contacts} for:item="contact">
          <p key={contact.Id}>{contact.Name}</p>
        </template>
      </template>
      <template lwc:elseif={error}>
        <c-error-panel errors={error}></c-error-panel>
      </template>
    </div>
  </lightning-card>
</template>

命令式调用 Apex 方法

若要控制方法调用的发生时间(例如,响应单击按钮),请以命令方式调用该方法。当您以命令方式调用某个方法时,您只收到一个响应。将此行为与 进行比较,后者将控制权委托给框架,并导致预配值流。@wire

在以下情况下,必须以命令方式调用 Apex 方法,而不是使用 .@wire

  • 调用未注释的方法,其中包括插入、更新或删除数据的任何方法。cacheable=true
  • 控制调用的发生时间。
  • 使用用户界面 API 不支持的对象,如 Task 和 Event。
  • 从不扩展的 ES6 模块调用方法LightningElement

如果 Apex 方法标记为 ,则在发出网络调用以调用服务器上的 Apex 方法之前,将检查客户端 Lightning 数据服务缓存。但是,Lightning 数据服务不管理 Apex 预置的数据。因此,要刷新过时的数据,请调用 Apex 方法,然后调用 notifyRecordUpdateAvailable(recordIds) 来更新 Lightning 数据服务缓存。@AuraEnabled(cacheable=true)

调用 Apex方法

让我们看一下 lwc-recipes 存储库中的一个示例组件,该组件使用与前面示例相同的类。当用户单击按钮时,组件不会连接它,而是调用 .getContactListgetContactList()

导入的函数返回一个 promise。此代码在给定一组参数的情况下提供一次性分辨率,而 @wire(apexMethod) 提供值流并支持动态参数。

// apexImperativeMethod.js
import { LightningElement, track } from "lwc";
import getContactList from "@salesforce/apex/ContactController.getContactList";

export default class ApexImperativeMethod extends LightningElement {
  @track contacts;
  @track error;

  handleLoad() {
    getContactList()
      .then((result) => {
        this.contacts = result;
      })
      .catch((error) => {
        this.error = error;
      });
  }
}
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Picture__c != NULL
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

该模板用于呈现联系人列表或错误面板。它还用于遍历联系人。lwc:iffor:each

<!-- apexImperativeMethod.html -->
<template>
  <lightning-card title="ApexImperativeMethod" icon-name="custom:custom63">
    <div class="slds-m-around_medium">
      <p class="slds-m-bottom_small">
        <lightning-button label="Load Contacts" onclick={handleLoad}></lightning-button>
      </p>
      <template lwc:if={contacts}>
        <template for:each={contacts} for:item="contact">
          <p key={contact.Id}>{contact.Name}</p>
        </template>
      </template>
      <template lwc:elseif={error}>
        <c-error-panel errors={error}></c-error-panel>
      </template>
    </div>
  </lightning-card>
</template>

使用参数调用 Apex 方法

将参数值传递给其属性与 Apex 方法的参数匹配的对象中的 Apex 方法。例如,如果 Apex 方法采用字符串参数,则不要直接传递字符串。相反,请传递一个对象,该对象包含值为字符串的属性。

// apexImperativeMethodWithParams.js

import { LightningElement } from "lwc";
import findContacts from "@salesforce/apex/ContactController.findContacts";

export default class ApexImperativeMethodWithParams extends LightningElement {
  searchKey = "";
  contacts;
  error;

  handleKeyChange(event) {
    this.searchKey = event.target.value;
  }

  handleSearch() {
    findContacts({ searchKey: this.searchKey })
      .then((result) => {
        this.contacts = result;
        this.error = undefined;
      })
      .catch((error) => {
        this.error = error;
        this.contacts = undefined;
      });
  }
}
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> findContacts(String searchKey) {
        String key = '%' + searchKey + '%';
        return [
            SELECT Id, Name, Title, Phone, Email, Picture__c
            FROM Contact
            WHERE Name LIKE :key AND Picture__c != NULL
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

提示

lwc-recipes 存储库也有一个组件,用于调用带有参数的方法。若要调用具有对象参数的方法,请签出组件。apexImperativeMethodWithParamsapexImperativeMethodWithComplexParams

将值传递给 Apex

将记录数据等值从 LWC 传递到 Apex 时,请使用 JavaScript 对象或数组。映射值在传递给 Apex 方法时不会序列化。

命令式和有线 Apex 调用都不支持使用映射。映射(例如 with )的不当使用允许在禁用 LWS 的情况下传递数据。但是,启用 LWS 后,此用法不再起作用。此外,不支持将值传递给 Apex。map[key] = valmap.set(key, val)

//Don’t do this
Map map = new Map();
map.set(key, val);
apexMethod({map});

您可以使用这样的 JavaScript 对象。

import { LightningElement, wire } from "lwc";
import apexMethod from "@salesforce/apex/ReadValues.apexMethod";

export default class ApexValueExample extends LightningElement {
  objVal = {};

  val;

  async connectedCallback() {
    this.objVal["one"] = "two";
    this.val = await apexMethod({ theValues: this.objVal });
  }

  @wire(apexMethod, { theValues: "$objVal" })
  propertyOrFunction;
}

Apex 操作的批处理

框架在将 Apex 操作发送到服务器之前将其排队。在编写代码时,此机制在很大程度上对您是透明的,但它使框架能够通过将多个操作批处理到一个请求 (XHR) 中来最大程度地减少网络流量。

动作的批处理也称为棚车,类似于将棚车耦合在一起的火车。

该框架使用堆栈来跟踪要发送到服务器的 Apex 操作。当浏览器在客户端上处理完事件和 JavaScript 时,堆栈上的排队操作将批量发送到服务器。

Boxcar 请求中的操作限制

如果 boxcar 请求中的操作超过 2,500 个,则框架将返回 413 HTTP 响应状态代码。如果用户看到此罕见错误,请考虑重新设计自定义组件,以遵循最佳实践并减少请求中的操作数。

Apex 方法结果的客户端缓存

若要提高运行时性能,请使用 批注 Apex 方法,这会在客户端上缓存方法结果。要设置 ,方法只能获取数据,不能改变(更改)数据。@AuraEnabled(cacheable=true)cacheable=true

将方法标记为可缓存可快速显示客户端存储中的缓存数据,而无需等待服务器访问,从而提高了组件的性能。如果缓存的数据已过时,框架将从服务器检索最新数据。缓存对于连接延迟高、速度慢或不可靠的用户特别有益。可缓存方法的性能更好,因此请尽可能使用它们。

要用于调用 Apex 方法,必须设置 .@wirecacheable=true

要以命令方式调用 Apex 方法,您可以选择将 .cacheable=true

缓存刷新时间是在存储中刷新条目之前的持续时间(以秒为单位)。刷新时间在 Lightning Experience 和 Salesforce 移动应用程序中自动配置。

默认缓存持续时间可能会随着平台的优化而更改。在设计 Lightning Web 组件时,不要假设缓存持续时间。如果组件知道缓存的值无效(例如,当基础数据发生更改时),它可以向服务器查询更新的数据,并使用 from 刷新缓存。refreshApex()@salesforce/apex

提示

lwc-recipes 存储库中的组件调用 .ldsDeleteRecordrefreshApex()

强制调用方法时刷新缓存

要刷新过时的 Apex 数据,请调用 Apex 方法,然后调用 notifyRecordUpdateAvailable(recordIds) 以更新 Lightning 数据服务 (LDS) 缓存。Lightning 数据服务不管理由命令性 Apex 调用预置的数据。调用 Apex 方法后,调用以向 Lightning 数据服务发出某些记录已过时的信号,并刷新缓存中的这些记录。notifyRecordUpdateAvailable(recordIds)

import { LightningElement, wire } from 'lwc';
import { getRecord, notifyRecordUpdateAvailable } from 'lightning/uiRecordApi';
import apexUpdateRecord from '@salesforce/apex/Controller.apexUpdateRecord';

export default class Example extends LightningElement {
    @api recordId;

    // Wire a record
    @wire(getRecord, { recordId: '$recordId', fields: ... })
    record;

    async handler() {
      // Do something before the record is updated
      showSpinner();

      // Update the record via Apex
      await apexUpdateRecord(this.recordId);

      // Notify LDS that you've changed the record outside its mechanisms
      // Await the Promise object returned by notifyRecordUpdateAvailable()
      await notifyRecordUpdateAvailable([{recordId: this.recordId}]);
      hideSpinner();
    }
}

使用@wire时刷新缓存

要刷新通过 Apex 置备的 Apex 数据,请调用 。该函数使用绑定到 的配置预配数据并更新缓存。@wirerefreshApex()@wire

注意

您刷新时使用的参数必须是以前由 Apex 发出的对象。refreshApex()@wire

有时,您知道缓存已过时。如果缓存已过时,则组件需要新数据。若要在服务器中查询更新的数据并刷新缓存,请导入并调用该函数。仅在必要时调用,因为它会导致查询服务器的网络行程。refreshApex()refreshApex()

该函数返回一个 Promise。解析 Promise 后,线路中的数据是最新的。Apex 方法的返回值仅在 上可用。当 Promise 解析时,有新数据,但 Promise 解析到的实际值是没有意义的。使用块处理刷新的数据,例如在页面上设置属性。refreshApex()@wire@wirethen()

import { refreshApex } from "@salesforce/apex";
refreshApex(valueProvisionedByApexWireService);
  • 其中 是用 Apex 注释的属性,或者如果注释了函数,则为有线函数接收的参数。valueProvisionedByApexWireService@wire// Example of refreshing data for a wired property // after you update data via an LDS-managed module (lightning/uiRecordApi). import { updateRecord } from 'lightning/uiRecordApi'; import { refreshApex } from '@salesforce/apex'; import getOpptyOverAmount from '@salesforce/apex/OpptyController.getOpptyOverAmount; @wire(getOpptyOverAmount, { amount: '$amount' }) opptiesOverAmount; // Update the record using updateRecord(recordInput) // Refresh Apex data that the wire service provisioned handler() { updateRecord(recordInput).then(() => { refreshApex(this.opptiesOverAmount); }); }// Example of refreshing data for a wired property // after you update data via imperative Apex. import { refreshApex } from '@salesforce/apex'; import { notifyRecordUpdateAvailable } from 'lightning/uiRecordApi'; import getOpptyOverAmount from '@salesforce/apex/OpptyController.getOpptyOverAmount; @wire(getOpptyOverAmount, { amount: '$amount' }) opptiesOverAmount; // Update the record in Apex, such as via a button click // Refresh Apex data that the wire service provisioned handler() { updateRecordApexMethod() .then(() => { refreshApex(this.opptiesOverAmount); notifyRecordUpdateAvailable(recordIds); // Refresh the Lightning Data Service cache }); }//Example of refreshing data for a wired function import { refreshApex } from '@salesforce/apex'; import getActivityHistory from '@salesforce/apex/ActivityController.getActivityHistory; @wire(getActivityHistory, { accountId: '$recordId', max: '500' }) wiredGetActivityHistory(value) { // Hold on to the provisioned value so we can refresh it later. this.wiredActivities = value; // Destructure the provisioned value const { data, error } = value; if (data) { ... } else if (error) { ... } ... } handler() { refreshApex(this.wiredActivities); }

示例:刷新有线属性的缓存

让我们看一个例子。此组件显示超过指定数量的商机列表。用户可以单击以将所有商机标记为“已赢得”。当机会更新时,缓存数据将变得过时。因此,在代码更新商机后,它会调用以查询服务器以获取更新的数据并刷新缓存。refreshApex()

<!-- opportunitiesOverAmount.html -->
<template>
  <!-- Display a list of opportunities -->
  <p>
    List of opportunities over
    <lightning-formatted-number
      value={amount}
      format-style="currency"
    ></lightning-formatted-number>
  </p>
  <template lwc:if={opptiesOverAmount.data}>
    <template for:each={opptiesOverAmount.data} for:item="oppty" for:index="idx">
      <div key={oppty.Id} class="slds-m-bottom_medium">
        <p>{idx}. {oppty.Name}</p>
        <p>
          <lightning-formatted-number
            value={oppty.Amount}
            format-style="currency"
          ></lightning-formatted-number>
        </p>
        <p>
          <lightning-formatted-date-time value={oppty.CloseDate}></lightning-formatted-date-time>
        </p>
        <p><lightning-badge label={oppty.StageName}></lightning-badge></p>
      </div>
    </template>
    <!-- Click the button to change the opportunities. Requires the data to be refetched
             and rerendered -->
    <lightning-button label="Mark all as Closed Won" onclick={handleClick}></lightning-button>
  </template>
</template>

让我们看一下组件的 JavaScript 代码。首先,它从 导入 Apex 方法。<c-opportunities-over-amount>refreshApex@salesforce/apex

然后,它使用动态值 调用 Apex 方法。每当发生更改时,都会重新请求数据。系统从客户端缓存或服务器提供数据。getOpptyOverAmountamountthis.amount

// opportunitiesOverAmount.js
import { LightningElement, api, wire } from "lwc";
import { refreshApex } from "@salesforce/apex";
import getOpptyOverAmount from "@salesforce/apex/OpptiesOverAmountApex.getOpptyOverAmount";
import updateOpptyStage from "@salesforce/apex/OpptiesOverAmountApex.updateOpptyStage";

export default class OpportunitiesOverAmount extends LightningElement {
  @api amount = 500000;

  @wire(getOpptyOverAmount, { amount: "$amount" })
  opptiesOverAmount;

  handleClick(e) {
    updateOpptyStage({
      amount: this.amount,
      stage: "Closed Won",
    })
      .then(() => {
        refreshApex(this.opptiesOverAmount).then(() => {
          // do something with the refreshed data in this.opptiesOverAmount
        });
      })
      .catch((error) => {
        this.message =
          "Error received: code" + error.errorCode + ", " + "message " + error.body.message;
      });
  }
}

代码在方法中调用。将 Closed Won 更改为 Closed Won 会影响字段,这意味着数据已过时。当您将来有关闭日期时,“关闭日期”字段将更改为今天的日期。refreshApex()handleClickstageCloseDate@wire

要告知系统数据已过时,请调用 。有线适配器将其缓存标记为过时,重新查询服务器以获取更新的数据,更新其缓存,然后通知其订阅者。发生这种情况时,组件的属性会更新,从而触发使用新数据重新呈现。收盘日期现在是(今天的日期),机会阶段现在是“Closed Won”。refreshApex()this.opptiesOverAmountJuly 18, 2019

该类包含该方法,该方法检索超过指定数量的机会列表。它还包含该方法,该方法使用 DML 操作将超过指定金额的商机阶段更新为“Closed Won”。OpptiesOverAmountApexgetOpptyOverAmountupdateOpptyStageupdate

public with sharing class OpptiesOverAmountApex {
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> getOpptyOverAmount(Decimal amount) {
        return [SELECT Id, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Amount > :amount];
    }

    @AuraEnabled
    public static void updateOpptyStage(Decimal amount, String stage) {
        for (List<Opportunity> oppts:
            [SELECT Id, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Amount > :amount]) {
                for(Opportunity o : oppts) {
                    o.StageName = stage;
                }
                update oppts;
            }
            return;
    }
}

示例:刷新有线函数的缓存

要刷新有线函数,请将有线函数接收的参数(即有线值)传递给 。在此示例代码中,有线函数为 。保留线路服务提供的值并将其传递给 。refreshApex()wiredGetActivityHistory(value)refreshApex()

使用新函数进行命令式调用。要跟踪已置备的值,请定义一个新属性,并在 中使用它。解构预置的值以轻松提取 和 对象。refreshApex()wiredActivitieswiredGetActivityHistory(value)dataerror

import { LightningElement, api, wire } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getActivityHistory from '@salesforce/apex/GalleryApexController.getActivityHistory';

export default class GalleryApexMaster extends LightningElement {
    @api recordId;
    wiredActivities;

    @wire(getActivityHistory, { accountId: '$recordId', max: '500' })
    wiredGetActivityHistory(value) {
        // Hold on to the provisioned value so we can refresh it later.
        this.wiredActivities = value; // track the provisioned value
        const { data, error } = value; // destructure the provisioned value
        if (data) { ... }
        else if (error) { ... }
        ...
    }

    handleLogACall() {
        // Use the value to refresh wiredGetActivityHistory().
        return refreshApex(this.wiredActivities);
    }
}

从中导入对象和字段@salesforce/schema

如果您导入对对象和字段的引用,Salesforce 会验证对象和字段是否存在(这也会捕获拼写错误),防止删除对象和字段,并将任何重命名的对象和字段级联到组件的源代码中。它还确保依赖对象和字段包含在更改集和包中。导入对对象和字段的引用可确保代码正常工作,即使对象和字段名称发生更改也是如此。

如果组件通过 Apex 类获取对象并从 导入对对象的引用,则调用以从对象中获取字段值。@salesforce/schemagetSObjectValue()

getSObjectValue(sobject, fieldApiName);
  • {sobject}– Apex 方法返回的对象。
  • {fieldApiName}– 字段的 API 名称。该值可以是字符串,也可以是对从 导入的字段的引用。您最多可以指定 5 个级别的生成字段。例如,返回 4 级生成字段。@salesforce/schemaOpportunity.Account.CreatedBy.LastModifiedById

让我们看一下 lwc-recipes 中的组件。该组件从返回单个联系人的 Apex 方法获取其数据。apexStaticSchema

// ContactController.cls
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static Contact getSingleContact(){
        return [SELECT Id, Name, Title, Phone, Email, Picture__c FROM Contact LIMIT 1];
    }

}

JavaScript 从 导入函数和方法。它从 导入对联系人字段的引用。getSObjectValuegetSingleContact@salesforce/apex@salesforce/schema

Apex 方法已连接到属性。模板中的 、 和 属性具有 getter,这些 getter 调用以从指定字段中提取值。contactnametitleemailgetSObjectValue()

// apexStaticSchema.js
import { LightningElement, wire } from "lwc";
import { getSObjectValue } from "@salesforce/apex";
import getSingleContact from "@salesforce/apex/ContactController.getSingleContact";

import NAME_FIELD from "@salesforce/schema/Contact.Name";
import TITLE_FIELD from "@salesforce/schema/Contact.Title";
import EMAIL_FIELD from "@salesforce/schema/Contact.Email";

export default class ApexStaticSchema extends LightningElement {
  @wire(getSingleContact) contact;

  get name() {
    return this.contact.data ? getSObjectValue(this.contact.data, NAME_FIELD) : "";
  }
  get title() {
    return this.contact.data ? getSObjectValue(this.contact.data, TITLE_FIELD) : "";
  }
  get email() {
    return this.contact.data ? getSObjectValue(this.contact.data, EMAIL_FIELD) : "";
  }
}
<!-- apexStaticSchema.html -->
<template>
  <lightning-card title="ApexStaticSchema" icon-name="custom:custom63">
    <template lwc:if={contact.data}>
      <div class="slds-m-around_medium">
        <p>{name}</p>
        <p>{title}</p>
        <p><lightning-formatted-email value={email}></lightning-formatted-email></p>
      </div>
    </template>
    <template lwc:elseif={contact.error}>
      <c-error-panel errors={contact.error}></c-error-panel>
    </template>
  </lightning-card>
</template>

使用延续进行长时间的标注

使用 Apex 中的类向外部 Web 服务发出长时间运行的请求。在回调方法中处理响应。延续是管理宣传信息的首选方式,因为它们可以大幅改善用户体验。Continuation

使用延续有一些优点,包括能够并行进行标注。

框架在将操作发送到服务器之前对操作进行排队。在编写代码时,此机制在很大程度上对您是透明的,但它使框架能够通过将多个操作批处理到一个请求 (XHR) 中来最大程度地减少网络流量。动作的批处理也称为棚车,类似于将棚车耦合在一起的火车。由于延续可以是长时间运行的请求,因此该框架实质上将延续视为后台操作。延续不会与其他请求绑定,因此它们在运行时不会阻止其他操作。

使用延续发出的异步标注不计入持续时间超过 5 秒的同步请求的 Apex 限制。自 20 年冬季以来,所有标注都被排除在长期运行的请求限制之外,因此与常规标注相比,延续不再为使用限制提供优势。但是,由于改善了用户体验,我们建议使用延续来管理宣传信息。

在 Apex 类中使用延续

若要使用 Apex 类中的延续,请使用 Apex 对象。Continuation

先决条件

在调用外部服务之前,您必须将远程站点添加到 Salesforce 用户界面中的授权远程站点列表中。在“设置”的“快速查找”框中,输入 。选择远程站点设置”,然后单击“新建远程站点”。添加与下面的 Apex 类延续示例中对应的标注 URL。Remote Site SettingsLONG_RUNNING_SERVICE_URL

注意

如果标注将命名凭据指定为终结点,则无需配置远程站点设置。命名凭据在一个定义中指定标注终结点的 URL 及其所需的身份验证参数。在代码中,指定命名凭据 URL,而不是长时间运行的服务 URL。

使用 Continuation 对象

  1. 若要进行长时间运行的标注,请定义一个返回对象的 Apex 方法。(先不用担心注解的属性。我们很快就会解释。Continuation@AuraEnabled@AuraEnabled(continuation=true cacheable=true) public static Object startRequest() { // Create continuation. Argument is timeout in seconds. Continuation con = new Continuation(40); // more to come here return con; }
  2. 在对象的属性中设置标注完成后要调用的 Apex 回调方法。在此示例中,回调方法为 。回调方法必须位于同一 Apex 类中。continuationMethodContinuationprocessResponsecon.continuationMethod='processResponse';
  3. 通过向标注对象添加对象来设置标注的端点。单个对象最多可以包含三个注解。每个标注都必须在安装程序中定义一个远程站点或命名凭据。HttpRequestContinuationContinuationHttpRequest req = new HttpRequest(); req.setMethod('GET'); req.setEndpoint(LONG_RUNNING_SERVICE_URL); con.addHttpRequest(req);
  4. 在对象的属性中设置要传递给回调方法的数据。该属性具有类型,因此可以传入 Apex 中支持的任何数据类型。stateContinuationstateObjectcon.state='Hello, World!';
  5. 在 Apex 回调中编写逻辑代码。当对象中设置的所有标注都完成后,将调用 Apex 回调方法 。回调方法有两个可以访问的参数。ContinuationprocessResponsepublic static Object processResponse(List<String> labels, Object state)
    1. labels– 标签列表,对应于延续中的每个请求。这些标签是自动创建的。
    2. state– 在对象的属性中设置的状态。stateContinuation
  6. 获取延续中每个请求的响应。例如:HttpResponse response = Continuation.getResponse(labels[0]);
  7. 将结果返回到组件的 JavaScript 文件。

示例:具有延续的 Apex 类

下面是一个完整的 Apex 类,它将所有前面的步骤联系在一起。

public with sharing class SampleContinuationClass {
    // Callout endpoint as a named credential URL
    // or, as shown here, as the long-running service URL
    private static final String LONG_RUNNING_SERVICE_URL =
        '<insert your callout URL here>';

    // Action method
    @AuraEnabled(continuation=true cacheable=true)
    public static Object startRequest() {
      // Create continuation. Argument is timeout in seconds.
      Continuation con = new Continuation(40);
      // Set callback method
      con.continuationMethod='processResponse';
      // Set state
      con.state='Hello, World!';
      // Create callout request
      HttpRequest req = new HttpRequest();
      req.setMethod('GET');
      req.setEndpoint(LONG_RUNNING_SERVICE_URL);
      // Add callout request to continuation
      con.addHttpRequest(req);
      // Return the continuation
      return con;
    }

    // Callback method
    @AuraEnabled(cacheable=true)
    public static Object processResponse(List<String> labels, Object state) {
      // Get the response by using the unique label
      HttpResponse response = Continuation.getResponse(labels[0]);
      // Set the result variable
      String result = response.getBody();
      return result;
    }
}

@AuraEnabled延续的注释

延续使用 Apex 代码的注释。以下是使用规则。@AuraEnabled

  • @AuraEnabled(continuation=true)返回延续的 Apex 控制器方法必须用 注释。@AuraEnabled(continuation=true)
  • @AuraEnabled(continuation=true cacheable=true)若要缓存延续操作的结果,请在 Apex 回调方法的注释上设置。cacheable=true注意之间有一个空格,而不是逗号continuation=true cacheable=true

缓存注意事项

最佳做法是设置延续链中涉及的所有方法,包括返回对象的方法。cacheable=trueContinuation

在此示例中,返回延续 的 Apex 方法和回调 ,都包含在其批注中。startRequest()processResponse()cacheable=true@AuraEnabled

// Action method
@AuraEnabled(continuation=true cacheable=true)
public static Object startRequest() { }

// Callback method
@AuraEnabled(cacheable=true)
public static Object processResponse(List<String> labels,
  Object state) { }

可缓存设置行为

下表总结了 中属性的不同设置的行为。cacheable@AuraEnabled

返回 Continuation 对象的方法 注释cacheable=true回调方法注解cacheable=true有效?可以使用 ?@wire操作响应是否缓存在客户端上?
是的是的是的是的是的
是的否(引发异常)不适用不适用
是的是的否(所有方法都必须具有cacheable=true)
是的

延续示例

下面是一个组件的标记,该组件在单击按钮后调用线路服务的延续和命令。

<!-- continuationCmp.html -->
<template>
  <lightning-button label="Call Continuation" onclick={callContinuation}> </lightning-button>
  <div>@wire(startRequest) result: {formattedWireResult}</div>
  <div>Imperative result: {formattedImperativeResult}</div>
</template>

HTML 包含一个按钮,用于触发对延续的命令性调用。当我们接下来查看 JavaScript 文件时,我们会看到更多。

下面是组件的 JavaScript 文件。

import { LightningElement, track, wire } from "lwc";
import startRequest from "@salesforce/apexContinuation/SampleContinuationClass.startRequest";
export default class ContinuationComponent extends LightningElement {
  @track imperativeContinuation = {};

  // Using wire service
  @wire(startRequest)
  wiredContinuation;

  get formattedWireResult() {
    return JSON.stringify(this.wiredContinuation);
  }

  // Imperative Call
  callContinuation() {
    startRequest()
      .then((result) => {
        this.imperativeContinuation = result;
      })
      .catch((error) => {
        this.imperativeContinuation = error;
      });
  }

  get formattedImperativeResult() {
    return JSON.stringify(this.imperativeContinuation);
  }
}

此代码使用新的导入语法。

import startRequest from "@salesforce/apexContinuation/SampleContinuationClass.startRequest";

此 import 语句告诉框架,我们正在调用一个可以返回延续的 Apex 方法。如果 Apex 方法返回一个对象,则必须使用 批注该方法。startRequestContinuation@AuraEnabled(continuation=true)

该组件使用线路服务调用 Apex 方法。线路服务将控制流委托给 Lightning Web 组件引擎。在执行延续后,当电缆适配器中的数据变得可用时,模板将显示结果。您可以使用线路服务,因为在 Apex 类中进行了注释。startRequeststartRequest()@AuraEnabled(cacheable=true)

由于延续会发出长时间运行的请求,因此您可能更愿意通过发出命令性调用来控制延续执行的开始时间,而不是使用线路服务。此组件使用有线服务和命令性调用。有线服务调用延续并缓存结果。当您单击该按钮对同一 Apex 方法进行命令性调用时,缓存的结果将返回并显示在模板中。

延续限制

由于延续可能导致多个长时间运行的操作,因此它们的使用存在一些限制。

《Apex 开发人员指南》中列出了在 Apex 中使用延续的限制。

以下是一些特定于 Lightning Web 组件使用的限制。每个延续最多 3 个标注

单个对象最多可以包含三个注解。Continuation连续操作的串行处理

该框架从客户端串行处理包含延续的操作。在进行下一次延续调用之前,必须完成上一次延续调用。在任何时候,客户端上只能有一个正在进行的延续。DML 操作限制

返回对象的 Apex 方法不能执行任何数据操作语言 (DML) 操作。DML 语句在 Salesforce 中插入、更新、合并、删除和恢复数据。如果在延续方法中执行了任何 DML,则延续执行不会继续,事务将回滚,并返回错误。Continuation

您可以在 Apex 回调方法中执行 DML 操作以延续。

保护 Apex 类

默认情况下,Apex 在系统模式下运行,这意味着它以大幅提升的权限运行,就像用户拥有大多数权限以及授予的所有字段和对象级访问权限一样。由于这些安全层不像在 Salesforce UI 中那样强制执行,因此您必须编写代码来强制执行它们。否则,您的组件可能会无意中暴露敏感数据,而这些数据通常会在 Salesforce UI 中对用户隐藏。

注意

要使用 Salesforce 记录,我们建议使用 Lightning 数据服务,它为您处理共享规则、CRUD 和字段级安全性。请参阅数据指南。

授予用户对 Apex 类的访问权限

仅当用户的配置文件或分配的权限集允许访问 Apex 类时,经过身份验证的用户或来宾用户才能访问 Apex 方法。@AuraEnabled

有关配置用户配置文件或权限集对 Apex 类的访问的详细信息,请参阅《Apex 开发人员指南》中的类安全性。

强制执行共享规则

声明类时,最佳做法是指定在组件使用 Apex 控制器时强制执行共享规则。with sharing

public with sharing class SharingClass {
    // Code here
}

未显式设置 或 定义的 Apex 类,或者使用 定义的 Apex 类使用默认值或隐式值。但是,Apex 类不会显式设置或继承其运行上下文中的值。因此,当设置其中一个关键字的类调用没有显式共享行为的类时,它将与调用类的共享行为一起操作。要确保您的班级强制执行共享规则,请将 .@AuraEnabledwith sharingwithout sharinginherited sharingwith sharingwith sharingwithout sharingwith sharing

关键字强制执行记录级安全性。它不强制实施对象级和字段级安全性。您必须在 Apex 类中分别手动强制实施对象级和字段级安全性。with sharing

强制执行对象和字段权限(CRUD 和 FLS)

有几种替代方法可以在 Apex 代码中强制执行对象级和字段级权限。最简单的强制执行方式WITH SECURITY_ENFORCED

若要强制执行对象级和字段级权限,请在 Apex 代码中将该子句用于查询,包括子查询和跨对象关系。WITH SECURITY_ENFORCEDSOQL SELECT

如果你在开发安全代码方面经验最少,并且对于不需要对权限错误进行正常降级的应用程序,则该子句是理想的选择。WITH SECURITY_ENFORCED

此示例使用不安全的方法 查询自定义支出对象上的字段。不要使用这个类!get_UNSAFE_Expenses()

// This class is an anti-pattern.
public with sharing class UnsafeExpenseController {
    // ns refers to namespace; leave out ns__ if not needed
    // This method is vulnerable because it doesn't enforce FLS.
    @AuraEnabled
    public static List<ns__Expense__c> get_UNSAFE_Expenses() {
        return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
            ns__Reimbursed__c, CreatedDate FROM ns__Expense__c];
    }
}

下一个示例使用安全方法 ,该方法使用子句强制执行对象级和字段级权限。使用此类代替 .getExpenses()WITH SECURITY_ENFORCEDUnsafeExpenseController

public with sharing class ExpenseController {
    // This method is recommended because it enforces FLS.
    @AuraEnabled
    public static List<ns__Expense__c> getExpenses() {
    // Query the object safely
    return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
            ns__Reimbursed__c, CreatedDate
             FROM ns__Expense__c WITH SECURITY_ENFORCED];
    }
}

有关详细信息,请参阅 Apex 开发人员指南。优雅降解stripInaccessible()

若要更优雅地降级权限错误,请使用该方法强制实施字段级和对象级数据保护。此方法从用户无法访问的查询和子查询结果中删除字段和关系字段。如果需要,您可以查看是否有任何字段被剥离,并抛出带有自定义错误消息的字段。stripInaccessible()AuraHandledException

您还可以使用该方法在 DML 操作之前删除无法访问的 sObject 字段,以避免异常并清理已从不受信任的源反序列化的 sObject。

此示例将更新为使用 SOQL 子句。结果是相同的,但让您有机会在使用 时正常降级,而不是因访问冲突而失败。ExpenseControllerstripInaccessible()WITH SECURITY_ENFORCEDstripInaccessible()WITH SECURITY_ENFORCED

public with sharing class ExpenseControllerStripped {

    @AuraEnabled
    public static List<ns__Expense__c> getExpenses() {
        // Query the object but don't use WITH SECURITY_ENFORCED
        List<ns__Expense__c> expenses =
            [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
                ns__Reimbursed__c, CreatedDate
                 FROM ns__Expense__c];

         // Strip fields that are not readable
         SObjectAccessDecision decision = Security.stripInaccessible(
               AccessType.READABLE,
               expenses);

         // Throw an exception if any data was stripped
         if (!decision.getModifiedIndexes().isEmpty()) {
            throw new AuraHandledException('Data was stripped');
         }

         return expenses;
    }
}

有关更多详细信息和示例,请参阅 Apex 开发人员指南。遗留代码 using 和 methodsDescribeSObjectResultDescribeFieldResult

在子句和方法可用之前,强制执行对象和字段权限的唯一方法是通过调用 and 方法来检查当前用户的访问权限级别。然后,如果用户具有必要的权限,则执行特定的 DML 操作或查询。WITH SECURITY_ENFORCEDstripInaccessible()Schema.DescribeSObjectResultSchema.DescribeFieldResult

例如,您可以调用 、 或 方法分别验证当前用户是否具有对 的读取、创建或更新访问权限。同样,公开访问控制方法,您可以调用这些方法来检查当前用户对字段的读取、创建或更新访问权限。isAccessibleisCreateableisUpdateableSchema.DescribeSObjectResultsObjectSchema.DescribeFieldResult

此示例使用 describe result 方法。此方法需要更多的样板代码行,因此我们建议改用子句或方法。WITH SECURITY_ENFORCEDstripInaccessible()

public with sharing class ExpenseControllerLegacy {
    @AuraEnabled
    public static List<ns__Expense__c> getExpenses() {
        String [] expenseAccessFields = new String [] {'Id',
                                                       'Name',
                                                       'ns__Amount__c',
                                                       'ns__Client__c',
                                                       'ns__Date__c',
                                                       'ns__Reimbursed__c',
                                                       'CreatedDate'
                                                       };


    // Obtain the field name/token map for the Expense object
    Map<String,Schema.SObjectField> m = Schema.SObjectType.ns__Expense__c.fields.getMap();

    for (String fieldToCheck : expenseAccessFields) {

        // Call getDescribe to check if the user has access to view field
        if (!m.get(fieldToCheck).getDescribe().isAccessible()) {

            // Pass error to client
            throw new System.NoAccessException();
        }
    }

    // Query the object safely
    return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c,
            ns__Reimbursed__c, CreatedDate FROM ns__Expense__c];
    }
}