{"id":3745,"date":"2023-12-20T17:32:10","date_gmt":"2023-12-20T09:32:10","guid":{"rendered":"http:\/\/www.ponybai.com\/?p=3745"},"modified":"2023-11-25T17:49:02","modified_gmt":"2023-11-25T09:49:02","slug":"salesforce-%e8%bf%9e%e6%8e%a5","status":"publish","type":"post","link":"http:\/\/www.ponybai.com\/?p=3745","title":{"rendered":"Salesforce \u8fde\u63a5"},"content":{"rendered":"\n<p>Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u3002\u4f7f\u7528 Apex Connector Framework\uff0c\u7528\u4e8e\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u6570\u636e\u5e76\u5728\u672c\u5730\u5408\u6210\u6570\u636e\u3002 Salesforce Connect \u5728 Salesforce \u5916\u90e8\u5bf9\u8c61\u4e2d\u8868\u793a\u8be5\u6570\u636e\uff0c\u4f7f\u7528\u6237\u548c Lightning Platform \u53ef\u4e0e\u5b58\u50a8\u5728 Salesforce \u5916\u90e8\u7684\u6570\u636e\u65e0\u7f1d\u4ea4\u4e92 \u7ec4\u7ec7\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Salesforce Connect \u5916\u90e8\u5bf9\u8c61\u7684 Apex \u6ce8\u610f\u4e8b\u9879 Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55\u00a0<strong>Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61<\/strong><br>\u6570\u636e\uff0c\u4f46\u5b58\u5728\u4e00\u4e9b\u8981\u6c42\u548c\u9650\u5236\u3002<\/li>\n\n\n\n<li>\u53ef\u5199\u7684\u5916\u90e8\u5bf9\u8c61 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c<strong>\u5916\u90e8\u5bf9\u8c61<\/strong><br>\u662f\u53ea\u8bfb\u7684\uff0c\u4f46\u60a8\u53ef\u4ee5\u4f7f\u5b83\u4eec\u53ef\u5199\u3002\u8fd9\u6837\uff0cSalesforce \u7528\u6237\u548c API \u5c31\u53ef\u4ee5\u901a\u8fc7\u4e0e\u7ec4\u7ec7\u5185\u7684\u5916\u90e8\u5bf9\u8c61\u8fdb\u884c\u4ea4\u4e92\u6765\u521b\u5efa\u3001\u66f4\u65b0\u548c\u5220\u9664\u5b58\u50a8\u5728\u7ec4\u7ec7\u5916\u90e8\u7684\u6570\u636e\u3002\u4f8b\u5982\uff0c\u7528\u6237\u53ef\u4ee5\u67e5\u770b\u9a7b\u7559\u5728 SAP \u7cfb\u7edf\u4e2d\u4e0e Salesforce \u4e2d\u7684\u5e10\u6237\u5173\u8054\u7684\u6240\u6709\u8ba2\u5355\u3002\u7136\u540e\uff0c\u5728\u4e0d\u79bb\u5f00 Salesforce \u7528\u6237\u754c\u9762\u7684\u60c5\u51b5\u4e0b\uff0c\u4ed6\u4eec\u53ef\u4ee5\u4e0b\u65b0\u8ba2\u5355\u6216\u8def\u7531\u73b0\u6709\u8ba2\u5355\u3002\u76f8\u5173\u6570\u636e\u5728SAP\u7cfb\u7edf\u4e2d\u81ea\u52a8\u521b\u5efa\u6216\u66f4\u65b0\u3002<\/li>\n\n\n\n<li>\u5916\u90e8\u53d8\u66f4\u6570\u636e\u6355\u83b7\u6253\u5305\u548c\u6d4b\u8bd5 \u60a8\u53ef\u4ee5\u5728\u6258\u7ba1\u5305\u4e2d\u5206\u53d1<strong>\u5916\u90e8\u53d8\u66f4\u6570\u636e<\/strong>\u6355\u83b7\u7ec4\u4ef6\uff0c\u5305\u62ec\u7528\u4e8e\u6d4b\u8bd5<br>Apex \u89e6\u53d1\u5668\u7684\u6846\u67b6\u3002\u7279\u6b8a\u884c\u4e3a\u548c\u9650\u5236\u9002\u7528\u4e8e\u5305\u88c5\u548c\u5305\u88c5\u5b89\u88c5\u3002<\/li>\n\n\n\n<li>Apex\u00a0<strong>\u8fde\u63a5\u5668\u6846\u67b6<\/strong><br>\u5165\u95e8 \u8981\u5f00\u59cb\u4f7f\u7528 Salesforce Connect \u7684\u7b2c\u4e00\u4e2a\u81ea\u5b9a\u4e49\u9002\u914d\u5668\uff0c\u8bf7\u521b\u5efa\u4e24\u4e2a Apex \u7c7b\uff1a\u4e00\u4e2a\u7528\u4e8e\u6269\u5c55\u7c7b\uff0c\u53e6\u4e00\u4e2a\u7528\u4e8e\u6269\u5c55\u8be5\u7c7b\u3002<samp>DataSource.ConnectionDataSource.Provider<\/samp><\/li>\n\n\n\n<li>\u5173\u4e8e Apex \u8fde\u63a5\u5668\u6846\u67b6<strong>\u7684\u5173\u952e\u6982\u5ff5 \u547d\u540d\u7a7a\u95f4\u63d0\u4f9b Apex \u8fde\u63a5\u5668\u6846\u67b6<\/strong><br>\u7684\u7c7b\u3002\u4f7f\u7528 Apex \u8fde\u63a5\u5668\u6846\u67b6\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u7136\u540e\uff0c\u901a\u8fc7 Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u5c06\u60a8\u7684 Salesforce \u7ec4\u7ec7\u8fde\u63a5\u5230\u4efb\u4f55\u4f4d\u7f6e\u7684\u4efb\u4f55\u6570\u636e\u3002<samp>DataSource<\/samp><\/li>\n\n\n\n<li>Apex Connector \u6846\u67b6<br>\u7684\u6ce8\u610f\u4e8b\u9879 \u4e86\u89e3\u4f7f\u7528\u00a0<strong>Apex Connector<\/strong>\u00a0Framework \u521b\u5efa Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u9650\u5236\u548c\u6ce8\u610f\u4e8b\u9879\u3002<\/li>\n\n\n\n<li>Apex \u8fde\u63a5\u5668\u6846\u67b6\u793a\u4f8b \u8fd9\u4e9b\u793a\u4f8b<br>\u8bf4\u660e\u5982\u4f55\u4f7f\u7528\u00a0<strong>Apex \u8fde\u63a5\u5668\u6846\u67b6<\/strong>\u4e3a Salesforce Connect \u521b\u5efa\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Salesforce Connect \u5916\u90e8\u7684 Apex \u6ce8\u610f\u4e8b\u9879 \u5bf9\u8c61<\/h2>\n\n\n\n<p>Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\uff0c\u4f46\u67d0\u4e9b \u9002\u7528\u8981\u6c42\u548c\u9650\u5236\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u8fd9\u4e9b\u529f\u80fd\u4e0d\u9002\u7528\u4e8e\u5916\u90e8\u5bf9\u8c61\u3002\n<ul class=\"wp-block-list\">\n<li>Apex \u6258\u7ba1\u5171\u4eab<\/li>\n\n\n\n<li>Apex \u89e6\u53d1\u5668\uff08\u4f46\u662f\uff0c\u60a8\u53ef\u4ee5\u9488\u5bf9\u5916\u90e8\u66f4\u6539\u6570\u636e\u521b\u5efa\u89e6\u53d1\u5668 \u4ece OData 4.0 \u8fde\u63a5\u6355\u83b7\u4e8b\u4ef6\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>\u5f53\u5f00\u53d1\u4eba\u5458\u4f7f\u7528 Apex \u64cd\u4f5c\u5916\u90e8\u5bf9\u8c61\u8bb0\u5f55\u65f6\uff0c\u5f02\u6b65 \u8ba1\u65f6\u548c\u6d3b\u52a8\u540e\u53f0\u961f\u5217\u53ef\u6700\u5927\u7a0b\u5ea6\u5730\u51cf\u5c11\u6f5c\u5728\u7684\u4fdd\u5b58\u51b2\u7a81\u3002\u4e00\u4e2a \u4e13\u95e8\u7684 Apex \u65b9\u6cd5\u548c\u5173\u952e\u5b57\u96c6\u53ef\u5904\u7406\u6f5c\u5728\u7684\u8ba1\u65f6\u95ee\u9898 \u5177\u6709\u5199\u5165\u6267\u884c\u529f\u80fd\u3002Apex \u8fd8\u5141\u8bb8\u60a8\u68c0\u7d22\u5220\u9664\u548c \u66f4\u65b0\u63d2\u5165\u64cd\u4f5c\u3002\u4f7f\u7528 BackgroundOperation \u5bf9\u8c61\u76d1\u89c6\u4f5c\u4e1a\u8fdb\u5ea6 \u7528\u4e8e\u901a\u8fc7 API \u6216 SOQL \u8fdb\u884c\u5199\u5165\u64cd\u4f5c\u3002<\/li>\n\n\n\n<li><a><\/a><samp>Database.insertAsync()<\/samp>\u65b9\u6cd5\u4e0d\u80fd\u5728 \u95e8\u6237\u7528\u6237\u7684\u4e0a\u4e0b\u6587\uff0c\u5373\u4f7f\u95e8\u6237\u7528\u6237\u662f\u793e\u533a\u6210\u5458\u4e5f\u662f\u5982\u6b64\u3002 \u8981\u901a\u8fc7 Apex \u6dfb\u52a0\u5916\u90e8\u5bf9\u8c61\u8bb0\u5f55\uff0c\u8bf7\u4f7f\u7528\u65b9\u6cd5\u3002<samp>Database.insertImmediate()<\/samp><\/li>\n<\/ul>\n\n\n\n<p>\u91cd\u8981<\/p>\n\n\n\n<p>\u9488\u5bf9\u5916\u90e8\u6570\u636e\u6e90\u8fd0\u884c\u53ef\u8fed\u4ee3\u7684\u6279\u5904\u7406 Apex \u4f5c\u4e1a\u65f6\uff0c \u4f5c\u4e1a\u8fd0\u884c\u65f6\uff0c\u5916\u90e8\u8bb0\u5f55\u5b58\u50a8\u5728 Salesforce \u4e2d\u3002\u6570\u636e\u5c06\u4ece \u4f5c\u4e1a\u5b8c\u6210\u65f6\u7684\u5b58\u50a8\uff0c\u65e0\u8bba\u4f5c\u4e1a\u662f\u5426\u6210\u529f\u3002\u6ca1\u6709\u5916\u90e8\u6570\u636e \u5728\u4f7f\u7528 .<samp>Database.QueryLocator<\/samp><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5982\u679c\u4f7f\u7528\u6279\u5904\u7406 Apex \u6765\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61 \u901a\u8fc7\u9002\u7528\u4e8e Salesforce \u7684 OData \u9002\u914d\u5668\uff1a<samp>Database.QueryLocator<\/samp><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u53ef\u5199\u7684\u5916\u90e8\u5bf9\u8c61<\/h2>\n\n\n\n<p>\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5916\u90e8\u5bf9\u8c61\u662f\u53ea\u8bfb\u7684\uff0c\u4f46\u60a8\u53ef\u4ee5\u4f7f\u5b83\u4eec\u53ef\u5199\u3002\u8fd9\u6837\u505a \u5141\u8bb8 Salesforce \u7528\u6237\u548c API \u521b\u5efa\u3001\u66f4\u65b0\u548c\u5220\u9664\u5b58\u50a8\u5728\u7ec4\u7ec7\u5916\u90e8\u7684\u6570\u636e \u901a\u8fc7\u4e0e\u7ec4\u7ec7\u5185\u7684\u5916\u90e8\u5bf9\u8c61\u8fdb\u884c\u4ea4\u4e92\u3002\u4f8b\u5982\uff0c\u7528\u6237\u53ef\u4ee5\u770b\u5230\u6240\u6709 \u9a7b\u7559\u5728 SAP \u7cfb\u7edf\u4e2d\u4e14\u4e0e Salesforce \u4e2d\u7684\u5e10\u6237\u5173\u8054\u7684\u8ba2\u5355\u3002\u7136\u540e \u5728\u4e0d\u79bb\u5f00 Salesforce \u7528\u6237\u754c\u9762\u7684\u60c5\u51b5\u4e0b\uff0c\u4ed6\u4eec\u53ef\u4ee5\u4e0b\u65b0\u8ba2\u5355\u6216\u8def\u7531 \u73b0\u6709\u8ba2\u5355\u3002\u76f8\u5173\u6570\u636e\u5728SAP\u4e2d\u81ea\u52a8\u521b\u5efa\u6216\u66f4\u65b0 \u7cfb\u7edf\u3002<\/p>\n\n\n\n<p>\u5bf9\u5916\u90e8\u6570\u636e\u7684\u8bbf\u95ee\u53d6\u51b3\u4e8e Salesforce \u4e0e\u5916\u90e8\u6570\u636e\u4e4b\u95f4\u7684\u8fde\u63a5 \u5b58\u50a8\u6570\u636e\u7684\u7cfb\u7edf\u3002\u7f51\u7edc\u5ef6\u8fdf\u548c\u5916\u90e8\u53ef\u7528\u6027 \u7cfb\u7edf\u53ef\u80fd\u4f1a\u5728\u5916\u90e8\u6267\u884c Apex \u5199\u5165\u6216\u5220\u9664\u64cd\u4f5c\u65f6\u5f15\u5165\u8ba1\u65f6\u95ee\u9898 \u5bf9\u8c61\u3002<\/p>\n\n\n\n<p>\u7531\u4e8e\u8fd9\u4e9b\u8fde\u63a5\u7684\u590d\u6742\u6027\uff0cApex \u65e0\u6cd5\u6267\u884c\u6807\u51c6\u3001\u6216\u64cd\u4f5c \u5728\u5916\u90e8\u5bf9\u8c61\u4e0a\u3002\u76f8\u53cd\uff0cApex \u63d0\u4f9b\u4e86\u4e00\u7ec4\u4e13\u95e8\u7684\u6570\u636e\u5e93\u65b9\u6cd5\u548c \u5173\u952e\u5b57\u6765\u89e3\u51b3\u5199\u5165\u6267\u884c\u7684\u6f5c\u5728\u95ee\u9898\u3002DML \u63d2\u5165\u3001\u66f4\u65b0\u3001 \u5bf9\u5916\u90e8\u5bf9\u8c61\u7684\u521b\u5efa\u548c\u5220\u9664\u64cd\u4f5c\u662f\u5f02\u6b65\u7684\u6216\u6267\u884c\u7684 \u5f53\u6ee1\u8db3\u7279\u5b9a\u6807\u51c6\u65f6\u3002<samp>insert()<\/samp><samp>update()<\/samp><samp>create()<\/samp>\u6b64\u793a\u4f8b\u4f7f\u7528\u8be5\u65b9\u6cd5\u5c06\u65b0\u8ba2\u5355\u5f02\u6b65\u63d2\u5165\u6570\u636e\u5e93\u8868\u4e2d\u3002\u5b83\u8fd4\u56de\u4e00\u4e2a\u5305\u542b\u552f\u4e00\u6807\u8bc6\u7b26\u7684\u5bf9\u8c61 \u7528\u4e8e\u63d2\u5165\u7269 \u5de5\u4f5c\u3002<\/p>\n\n\n\n<p><samp>Database.insertAsync()<\/samp><samp>SaveResult<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u200bpublic void createOrder () {\u200b   \n    SalesOrder__x order = new SalesOrder__x ();\u200b   \n    Database.SaveResult sr = Database.insertAsync (order);\u200b   \n    if (! sr.isSuccess ()) {\n        String locator =  Database.getAsyncLocator ( sr );\u200b     \n        completeOrderCreation(locator);\n    }\n\u200b}<\/code><\/pre>\n\n\n\n<p>\u6ce8\u610f<\/p>\n\n\n\n<p>\u901a\u8fc7 Salesforce \u7528\u6237\u754c\u9762\u6216 API \u5bf9\u5916\u90e8\u5bf9\u8c61\u6267\u884c\u5199\u5165 \u662f\u540c\u6b65\u7684\uff0c\u5176\u5de5\u4f5c\u65b9\u5f0f\u4e0e\u6807\u51c6\u5bf9\u8c61\u548c\u81ea\u5b9a\u4e49\u5bf9\u8c61\u76f8\u540c\u3002<\/p>\n\n\n\n<p>\u60a8\u53ef\u4ee5\u5f02\u6b65\u5bf9\u5916\u90e8\u5bf9\u8c61\u6267\u884c\u4ee5\u4e0b DML \u64cd\u4f5c \u6216\u57fa\u4e8e\u6761\u4ef6\uff1a\u63d2\u5165\u8bb0\u5f55\u3001\u66f4\u65b0\u8bb0\u5f55\u3001\u66f4\u65b0\u63d2\u5165\u8bb0\u5f55\u6216\u5220\u9664\u8bb0\u5f55\u3002 \u4f7f\u7528\u547d\u540d\u7a7a\u95f4\u4e2d\u7684\u7c7b\u83b7\u53d6 \u5f02\u6b65\u4f5c\u4e1a\u7684\u552f\u4e00\u6807\u8bc6\u7b26\uff0c\u6216\u68c0\u7d22 upsert \u7684\u7ed3\u679c\u5217\u8868\uff0c \u5220\u9664\u6216\u4fdd\u5b58\u64cd\u4f5c\u3002<samp>DataSource<\/samp><\/p>\n\n\n\n<p>\u5728\u5916\u90e8\u5bf9\u8c61\u4e0a\u542f\u52a8 Apex \u65b9\u6cd5\u65f6\uff0c\u5c06\u8c03\u5ea6\u4f5c\u4e1a\u5e76\u5c06\u5176\u653e\u7f6e\u5728 \u540e\u53f0\u4f5c\u4e1a\u961f\u5217\u3002\u4f7f\u7528 BackgroundOperation \u5bf9\u8c61\u53ef\u4ee5\u67e5\u770b\u4f5c\u4e1a\u72b6\u6001 \u7528\u4e8e\u901a\u8fc7 API \u6216 SOQL \u8fdb\u884c\u5199\u5165\u64cd\u4f5c\u3002\u76d1\u89c6\u4f5c\u4e1a\u8fdb\u5ea6\u548c\u76f8\u5173\u9519\u8bef \u7ec4\u7ec7\u3001\u63d0\u53d6\u7edf\u8ba1\u4fe1\u606f\u3001\u5904\u7406\u6279\u5904\u7406\u4f5c\u4e1a\uff0c\u6216\u67e5\u770b\u6307\u5b9a\u4e2d\u53d1\u751f\u7684\u9519\u8bef\u6570 \u65f6\u95f4\u6bb5\u3002<\/p>\n\n\n\n<p>\u6709\u5173\u4f7f\u7528\u4fe1\u606f\u548c\u793a\u4f8b\uff0c\u8bf7\u53c2\u9605\u6570\u636e\u5e93\u547d\u540d\u7a7a\u95f4\u548c\u6570\u636e\u6e90\u547d\u540d\u7a7a\u95f4\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u5916\u90e8\u53d8\u66f4\u6570\u636e\u6355\u83b7\u3001\u6253\u5305\u548c\u6d4b\u8bd5<\/h2>\n\n\n\n<p>\u60a8\u53ef\u4ee5\u5728\u6258\u7ba1\u5305\u4e2d\u5206\u53d1\u5916\u90e8\u53d8\u66f4\u6570\u636e\u6355\u83b7\u7ec4\u4ef6\uff0c \u5305\u62ec\u7528\u4e8e\u6d4b\u8bd5 Apex \u89e6\u53d1\u5668\u7684\u6846\u67b6\u3002\u7279\u6b8a\u884c\u4e3a\u548c\u9650\u5236 \u9002\u7528\u4e8e\u5305\u88c5\u548c\u5305\u88c5\u5b89\u88c5\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u901a\u8fc7\u9009\u62e9 \u5c06\u5916\u90e8\u66f4\u6539\u6570\u636e\u8ddf\u8e2a\u7ec4\u4ef6\u5305\u542b\u5728\u6258\u7ba1\u5305\u4e2d Apex \u7c7b\u7ec4\u4ef6\u7c7b\u578b\u5217\u8868\u4e2d\u7684\u6d4b\u8bd5\u3002\u89e6\u53d1\u5668\u3001\u6d4b\u8bd5\u3001\u5916\u90e8\u6570\u636e \u6e90\u3001\u5916\u90e8\u5bf9\u8c61\u548c\u5176\u4ed6\u76f8\u5173\u8d44\u6e90\u88ab\u5f15\u5165\u5230\u5305\u4e2d \u5206\u914d\u3002<\/li>\n\n\n\n<li>\u8bc1\u4e66\u4e0d\u53ef\u6253\u5305\u3002\u5982\u679c\u6253\u5305\u5916\u90e8\u6570\u636e\u6e90 \u6307\u5b9a\u8bc1\u4e66\uff0c\u8bf7\u786e\u4fdd\u8ba2\u9605\u8005\u7ec4\u7ec7\u5177\u6709\u6709\u6548\u7684\u8bc1\u4e66 \u540c\u540d\u3002<\/li>\n<\/ul>\n\n\n\n<p>\u4e3a\u4e86\u5e2e\u52a9\u60a8\u6d4b\u8bd5\u5916\u90e8\u53d8\u66f4\u6570\u636e\u6355\u83b7\u89e6\u53d1\u7684 Apex \u7c7b\uff0c\u4e0b\u9762\u662f\u4e00\u4e2a\u5355\u5143 \u6d4b\u8bd5\u89e6\u53d1\u5668\u5bf9\u6a21\u62df\u5916\u90e8\u66f4\u6539\u505a\u51fa\u53cd\u5e94\u7684\u4ee3\u7801\u793a\u4f8b\u3002<\/p>\n\n\n\n<p><strong>\u4f8b \u89e6\u53d1<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u200btrigger OnExternalProductChangeEventForAudit on Products__ChangeEvent (after insert) {\n    if (Trigger.new.size() != 1) return;\n    for (Products__ChangeEvent event: Trigger.new) {\n         Product_Audit__c audit = new Product_Audit__c(); \n         audit.Name = 'ProductChangeOn' + event.ExternalId;\n         audit.Change_Type__c = event.ChangeEventHeader.getChangeType();\n         audit.Audit_Price__c = event.Price__c;\n         audit.Product_Name__c = event.Name__c;\n         insert(audit);\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>\u9876\u70b9\u6d4b\u8bd5<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u200b@isTest\npublic class testOnExternalProductChangeEventForAudit {\n    static testMethod void testExternalProductChangeTrigger() { \n            \/\/ Create Change Event\n           Products__ChangeEvent event = new Products__ChangeEvent();\n            \/\/ Set Change Event Header Fields\n           EventBus.ChangeEventHeader header = new EventBus.ChangeEventHeader();\n           header.changeType='CREATE';\n           header.entityName='Products__x';\n           header.changeOrigin='here';\n           header.transactionKey = 'some';\n           header.commitUser = 'me';\n           event.changeEventHeader = header;\n           event.put('ExternalId', 'ParentExternalId');\n           event.put('Price__c', 5500);\n           event.put('Name__c', 'Coat');\n            \/\/ Publish the event to the EventBus\n           EventBus.publish(event);\n           Test.getEventBus().deliver();\n            \/\/ Perform assertion that the trigger was run\n           Product_Audit__c audit = &#91;SELECT name, Audit_Price__c, Product_Name__c FROM Product_Audit__c WHERE name = : 'ProductChangeOn'+ event.ExternalId LIMIT 1]; \n           System.assertEquals('ProductChangeOn'+ event.ExternalId, audit.Name); \n           System.assertEquals(5500, audit.Audit_Price__c); \n           System.assertEquals('Coat', audit.Product_Name__c); \n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Apex Connector \u6846\u67b6\u5165\u95e8<\/h2>\n\n\n\n<p>\u8981\u5f00\u59cb\u4f7f\u7528 Salesforce Connect \u7684\u7b2c\u4e00\u4e2a\u81ea\u5b9a\u4e49\u9002\u914d\u5668\uff0c\u8bf7\u521b\u5efa\u4e24\u4e2a Apex \u7c7b\uff1a\u4e00\u4e2a\u7528\u4e8e\u6269\u5c55\u7c7b\uff0c\u4e00\u4e2a\u7528\u4e8e\u6269\u5c55\u7c7b\u3002<\/p>\n\n\n\n<p><samp>DataSource.Connection<\/samp><samp>DataSource.Provider<\/samp><\/p>\n\n\n\n<p>\u8ba9\u6211\u4eec\u9010\u6b65\u5b8c\u6210\u793a\u4f8b\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u4ee3\u7801\u3002<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u521b\u5efa\u793a\u4f8b DataSource.Connection\u00a0\u7c7b \u9996\u5148\uff0c\u521b\u5efa\u4e00\u4e2a\u7c7b<br>\uff0c\u4f7f Salesforce \u80fd\u591f\u83b7\u53d6\u5916\u90e8\u7cfb\u7edf\u7684\u67b6\u6784\uff0c\u5e76\u5904\u7406\u5916\u90e8\u6570\u636e\u7684\u67e5\u8be2\u548c\u641c\u7d22\u3002<samp>DataSource.Connection<\/samp><\/li>\n\n\n\n<li>\u521b\u5efa\u793a\u4f8b DataSource.Provider \u7c7b<br>\u73b0\u5728\uff0c\u60a8\u9700\u8981\u4e00\u4e2a\u7c7b\u6765\u6269\u5c55\u548c\u8986\u76d6 \u4e2d\u7684\u51e0\u4e2a\u65b9\u6cd5\u3002<samp>DataSource.Provider<\/samp><\/li>\n\n\n\n<li>\u8bbe\u7f6e Salesforce Connect \u4ee5\u4f7f\u7528\u60a8\u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668 \u521b\u5efa \u548c \u7c7b\u540e\uff0cSalesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668<br>\u5c06\u5728\u201c\u8bbe\u7f6e\u201d\u4e2d\u53ef\u7528\u3002<samp>DataSource.ConnectionDataSource.Provider<\/samp><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">\u521b\u5efa\u793a\u4f8b&nbsp;DataSource.Connection&nbsp;\u7c7b<\/h2>\n\n\n\n<p>\u9996\u5148\uff0c\u521b\u5efa\u4e00\u4e2a\u7c7b \u4f7f Salesforce \u80fd\u591f\u83b7\u53d6\u5916\u90e8\u7cfb\u7edf\u7684\u67b6\u6784\u5e76\u5904\u7406\u67e5\u8be2\uff0c\u4ee5\u53ca \u641c\u7d22\u5916\u90e8\u6570\u636e\u3002<\/p>\n\n\n\n<p><samp>DataSource.Connection<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>global class SampleDataSourceConnection\n    extends DataSource.Connection {\n    global SampleDataSourceConnection(DataSource.ConnectionParams\n        connectionParams) {\n    }\n\/\/ Add implementation of abstract methods\n\/\/ ...<\/code><\/pre>\n\n\n\n<p>\u8be5\u7c7b\u5305\u542b\u8fd9\u4e9b\u65b9\u6cd5\u3002<\/p>\n\n\n\n<p><samp>DataSource.Connection<\/samp><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u67e5\u8be2<\/li>\n\n\n\n<li>\u641c\u7d22<\/li>\n\n\n\n<li>\u540c\u6b65<\/li>\n\n\n\n<li>upsert\u884c<\/li>\n\n\n\n<li>deleteRows<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u540c\u6b65<\/h2>\n\n\n\n<p>\u5f53\u7ba1\u7406\u5458\u5355\u51fb\u5916\u90e8\u6570\u636e\u6e90\u4e0a\u7684<strong>\u201c\u9a8c\u8bc1\u5e76\u540c\u6b65<\/strong>\u201d\u6309\u94ae\u65f6\uff0c\u5c06\u8c03\u7528\u8be5\u65b9\u6cd5 \u8be6\u60c5\u9875\u9762\u3002\u5b83\u8fd4\u56de\u63cf\u8ff0\u7ed3\u6784\u5143\u6570\u636e\u7684\u4fe1\u606f \u5916\u90e8\u7cfb\u7edf\u3002<samp>sync()<\/samp><\/p>\n\n\n\n<p>\u6ce8\u610f<\/p>\n\n\n\n<p>\u66f4\u6539\u7c7b\u7684\u65b9\u6cd5\u4e0d\u4f1a \u81ea\u52a8\u91cd\u65b0\u540c\u6b65\u4efb\u4f55\u5916\u90e8\u5bf9\u8c61\u3002<samp>sync<\/samp><samp>DataSource.Connection<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ...\n    override global List&lt;DataSource.Table&gt; sync() {\n        List&lt;DataSource.Table&gt; tables =\n            new List&lt;DataSource.Table&gt;();\n        List&lt;DataSource.Column&gt; columns;\n        columns = new List&lt;DataSource.Column&gt;();\n        columns.add(DataSource.Column.text('Name', 255));\n        columns.add(DataSource.Column.text('ExternalId', 255));\n        columns.add(DataSource.Column.url('DisplayUrl'));\n        tables.add(DataSource.Table.get('Sample', 'Title',\n            columns));\n        return tables;\n    }\n\/\/ ...<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u67e5\u8be2<\/h2>\n\n\n\n<p>\u5f53 SOQL \u67e5\u8be2\u65f6\u8c03\u7528\u8be5\u65b9\u6cd5 \u5728\u5916\u90e8\u5bf9\u8c61\u4e0a\u6267\u884c\u3002\u5c06\u81ea\u52a8\u751f\u6210 SOQL \u67e5\u8be2\uff0c\u5e76 \u5f53\u7528\u6237\u5728 Salesforce\u7684\u3002\u662f \u59cb\u7ec8\u53ea\u9488\u5bf9\u5355\u4e2a\u8868\u3002<samp>query<\/samp><samp>DataSource.QueryContext<\/samp><\/p>\n\n\n\n<p>\u6b64\u793a\u4f8b\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u4f7f\u7528\u7c7b\u4e2d\u7684\u5e2e\u52a9\u7a0b\u5e8f\u65b9\u6cd5\u5bf9\u7ed3\u679c\u8fdb\u884c\u7b5b\u9009\u548c\u6392\u5e8f\uff0c\u5e76\u6839\u636e SOQL \u67e5\u8be2\u4e2d\u7684 and \u5b50\u53e5\u3002<samp>DataSource.QueryUtils<\/samp><samp>WHERE<\/samp><samp>ORDER BY<\/samp><\/p>\n\n\n\n<p>\u7c7b\u53ca\u5176 \u5e2e\u52a9\u7a0b\u5e8f\u65b9\u6cd5\u53ef\u4ee5\u5728 Salesforce \u7ec4\u7ec7\u5185\u672c\u5730\u5904\u7406\u67e5\u8be2\u7ed3\u679c\u3002\u8fd9 \u63d0\u4f9b\u8bfe\u7a0b\u662f\u4e3a\u4e86\u60a8\u7684\u65b9\u4fbf\uff0c\u4ee5\u7b80\u5316\u60a8\u7684 \u7528\u4e8e\u521d\u59cb\u6d4b\u8bd5\u7684 Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u4f46\u662f\uff0c\u8be5\u7c7b\u53ca\u5176\u65b9\u6cd5 \u4e0d\u652f\u6301\u5728\u4f7f\u7528\u6807\u6ce8\u8fdb\u884c\u68c0\u7d22\u7684\u751f\u4ea7\u73af\u5883\u4e2d\u4f7f\u7528 \u6765\u81ea\u5916\u90e8\u7cfb\u7edf\u7684\u6570\u636e\u3002\u5b8c\u6210\u5bf9\u5916\u90e8\u7684\u8fc7\u6ee4\u548c\u6392\u5e8f \u7cfb\u7edf\uff0c\u7136\u540e\u518d\u5c06\u67e5\u8be2\u7ed3\u679c\u53d1\u9001\u5230 Salesforce\u3002\u5982\u679c\u53ef\u80fd\uff0c\u8bf7\u4f7f\u7528 \u670d\u52a1\u5668\u9a71\u52a8\u7684\u5206\u9875\u6216\u5176\u4ed6\u6280\u672f\uff0c\u8ba9\u5916\u90e8\u7cfb\u7edf\u786e\u5b9a \u6839\u636e\u67e5\u8be2\u4e2d\u7684 limit \u548c offset \u5b50\u53e5\u8bbe\u7f6e\u9002\u5f53\u7684\u6570\u636e\u5b50\u96c6\u3002<samp>DataSource.QueryUtils<\/samp><samp>DataSource.QueryUtils<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ...\n    override global DataSource.TableResult query(\n        DataSource.QueryContext context) {\n        if (context.tableSelection.columnsSelected.size() == 1 &amp;&amp;\n            context.tableSelection.columnsSelected.get(0).aggregation ==\n                DataSource.QueryAggregation.COUNT) {\n                List&lt;Map&lt;String,Object&gt;&gt; rows = getRows(context);\n                List&lt;Map&lt;String,Object&gt;&gt; response =\n                    DataSource.QueryUtils.filter(context, getRows(context));\n                List&lt;Map&lt;String, Object&gt;&gt; countResponse =\n                    new List&lt;Map&lt;String, Object&gt;&gt;();\n                Map&lt;String, Object&gt; countRow =\n                    new Map&lt;String, Object&gt;();\n                countRow.put(\n                    context.tableSelection.columnsSelected.get(0).columnName,\n                    response.size());\n                countResponse.add(countRow);\n                return DataSource.TableResult.get(context,\n                    countResponse);\n        } else {\n            List&lt;Map&lt;String,Object&gt;&gt; filteredRows =\n                DataSource.QueryUtils.filter(context, getRows(context));\n            List&lt;Map&lt;String,Object&gt;&gt; sortedRows =\n                DataSource.QueryUtils.sort(context, filteredRows);\n            List&lt;Map&lt;String,Object&gt;&gt; limitedRows =\n                DataSource.QueryUtils.applyLimitAndOffset(context,\n                    sortedRows);\n            return DataSource.TableResult.get(context, limitedRows);\n        }\n    }\n\/\/ ...<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u641c\u7d22<\/h2>\n\n\n\n<p>\u8be5\u65b9\u6cd5\u7531 SOSL \u67e5\u8be2\u8c03\u7528 \u5916\u90e8\u5bf9\u8c61\u6216\u5f53\u7528\u6237\u6267\u884c Salesforce \u5168\u5c40\u641c\u7d22\u65f6\uff0c\u8be5\u641c\u7d22\u8fd8 \u641c\u7d22\u5916\u90e8\u5bf9\u8c61\u3002\u7531\u4e8e\u641c\u7d22\u53ef\u4ee5\u9488\u5bf9\u591a\u4e2a\u5bf9\u8c61\u8fdb\u884c\u8054\u5408\uff0c \u53ef\u4ee5\u6709 \u5df2\u9009\u62e9\u591a\u4e2a\u8868\u3002\u4f46\u662f\uff0c\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u77e5\u9053 \u53ea\u6709\u4e00\u5f20\u8868\u3002<samp>search<\/samp><samp>DataSource.SearchContext<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ...\n    override global List&lt;DataSource.TableResult&gt; search(\n            DataSource.SearchContext context) {\n        List&lt;DataSource.TableResult&gt; results =\n            new List&lt;DataSource.TableResult&gt;();\n        for (DataSource.TableSelection tableSelection :\n            context.tableSelections) {\n            results.add(DataSource.TableResult.get(tableSelection,\n                getRows(context)));\n        }\n        return results;\n    }\n\/\/ ...<\/code><\/pre>\n\n\n\n<p>\u4ee5\u4e0b\u662f helper \u65b9\u6cd5 \u641c\u7d22\u793a\u4f8b\u8c03\u7528\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u83b7\u53d6\u884c\u503c\u3002\u8be5\u65b9\u6cd5\u5229\u7528\u5176\u4ed6\u5e2e\u52a9\u7a0b\u5e8f \u65b9\u6cd5\uff1a<\/p>\n\n\n\n<p><samp>getRows<\/samp><samp>getRows<\/samp><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><samp>makeGetCallout<\/samp>\u5411 \u5916\u90e8\u7cfb\u7edf\u3002<\/li>\n\n\n\n<li><samp>foundRow<\/samp>\u6839\u636e \u6807\u6ce8\u7ed3\u679c\u4e2d\u7684\u503c\u3002\u8be5\u65b9\u6cd5\u7528\u4e8e\u5bf9 \u8fd4\u56de\u7684\u5b57\u6bb5\u503c\uff0c\u4f8b\u5982\u66f4\u6539\u5b57\u6bb5\u540d\u79f0\u6216\u4fee\u6539\u5b57\u6bb5 \u4ef7\u503c\u3002<samp>foundRow<\/samp><\/li>\n<\/ul>\n\n\n\n<p>\u8fd9\u4e9b\u65b9\u6cd5\u4e0d\u5305\u542b\u5728\u6b64\u4ee3\u7801\u7247\u6bb5\u4e2d\uff0c\u4f46\u5728\u5b8c\u6574\u793a\u4f8b\u4e2d\u53ef\u7528 \u5305\u542b\u5728\u8fde\u63a5\u7c7b\u4e2d\u3002\u901a\u5e38\uff0c\u8fc7\u6ee4\u5668 from \u6216\u5c06\u7528\u4e8e\u51cf\u5c11\u7ed3\u679c set\uff0c\u4f46\u4e3a\u7b80\u5355\u8d77\u89c1\uff0c\u6b64\u793a\u4f8b\u4e0d\u4f7f\u7528 context \u5bf9\u8c61\u3002<samp>SearchContextQueryContext<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ...\n    \/\/ Helper method to get record values from the external system for the Sample table.\n    private List&lt;Map&lt;String, Object&gt;&gt; getRows () {\n        \/\/ Get row field values for the Sample table from the external system via a callout.\n        HttpResponse response = makeGetCallout();\n        \/\/ Parse the JSON response and populate the rows.\n        Map&lt;String, Object&gt; m = (Map&lt;String, Object&gt;)JSON.deserializeUntyped(\n                response.getBody());\n        Map&lt;String, Object&gt; error = (Map&lt;String, Object&gt;)m.get('error');\n        if (error != null) {\n            throwException(string.valueOf(error.get('message')));\n        }\n        List&lt;Map&lt;String,Object&gt;&gt; rows = new List&lt;Map&lt;String,Object&gt;&gt;();\n        List&lt;Object&gt; jsonRows = (List&lt;Object&gt;)m.get('value');\n        if (jsonRows == null) {\n            rows.add(foundRow(m));\n        } else {\n            for (Object jsonRow : jsonRows) {\n                Map&lt;String,Object&gt; row = (Map&lt;String,Object&gt;)jsonRow;\n                rows.add(foundRow(row));\n            }\n        }\n        return rows;\n    }\n\/\/ ...<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">upsert\u884c<\/h2>\n\n\n\n<p>\u8be5\u65b9\u6cd5\u5728\u4ee5\u4e0b\u60c5\u51b5\u4e0b\u8c03\u7528 \u521b\u5efa\u6216\u66f4\u65b0\u5916\u90e8\u5bf9\u8c61\u8bb0\u5f55\u3002\u60a8\u53ef\u4ee5\u521b\u5efa\u6216\u66f4\u65b0\u5916\u90e8 \u901a\u8fc7 Salesforce \u7528\u6237\u754c\u9762\u6216 DML \u8fdb\u884c\u5bf9\u8c61\u8bb0\u5f55\u3002\u4ee5\u4e0b\u793a\u4f8b \u63d0\u4f9b\u8be5\u65b9\u6cd5\u7684\u793a\u4f8b\u5b9e\u73b0\u3002\u8be5\u793a\u4f8b\u4f7f\u7528\u4f20\u5165\u7684\u8868\u6765\u786e\u5b9a\u54ea\u4e2a\u8868\u662f selected \u5e76\u4ec5\u5728\u6240\u9009\u8868\u7684\u540d\u79f0\u4e3a \u65f6\u6267\u884c\u66f4\u65b0\u63d2\u5165\u3002upsert \u64cd\u4f5c\u5206\u4e3a \u63d2\u5165\u65b0\u8bb0\u5f55\u6216\u66f4\u65b0\u73b0\u6709\u8bb0\u5f55\u3002\u8fd9\u4e9b \u4f7f\u7528\u6807\u6ce8\u5728\u5916\u90e8\u7cfb\u7edf\u4e2d\u6267\u884c\u64cd\u4f5c\u3002\u6570\u7ec4\u4ece \u4ece\u6807\u6ce8\u54cd\u5e94\u4e2d\u83b7\u5f97\u7684\u7ed3\u679c\u3002\u8bf7\u6ce8\u610f\uff0c\u56e0\u4e3a\u6807\u6ce8\u662f\u9488\u5bf9 \u6bcf\u4e00\u884c\uff0c\u6b64\u793a\u4f8b\u53ef\u80fd\u4f1a\u8fbe\u5230 Apex \u6807\u6ce8\u9650\u5236\u3002<samp>upsertRows<\/samp><samp>upsertRows<\/samp><samp>UpsertContext<\/samp><samp>Sample<\/samp><samp>DataSource.UpsertResult<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ...\n    global override List&lt;DataSource.UpsertResult&gt; upsertRows(DataSource.UpsertContext \n            context) {\n       if (context.tableSelected == 'Sample') {\n           List&lt;DataSource.UpsertResult&gt; results = new List&lt;DataSource.UpsertResult&gt;();\n           List&lt;Map&lt;String, Object&gt;&gt; rows = context.rows;\n           \n           for (Map&lt;String, Object&gt; row : rows){\n              \/\/ Make a callout to insert or update records in the external system.\n              HttpResponse response;\n              \/\/ Determine whether to insert or update a record.\n              if (row.get('ExternalId') == null){\n                 \/\/ Send a POST HTTP request to insert new external record.\n                 \/\/ Make an Apex callout and get HttpResponse.\n                 response = makePostCallout(\n                     '{\"name\":\"' + row.get('Name') + '\",\"ExternalId\":\"' + \n                     row.get('ExternalId') + '\"');\n              }\n              else {\n                 \/\/ Send a PUT HTTP request to update an existing external record.\n                 \/\/ Make an Apex callout and get HttpResponse.\n                 response = makePutCallout(\n                     '{\"name\":\"' + row.get('Name') + '\",\"ExternalId\":\"' + \n                     row.get('ExternalId') + '\"',\n                     String.valueOf(row.get('ExternalId')));\n              }\n         \n              \/\/ Check the returned response.\n              \/\/ Deserialize the response.\n              Map&lt;String, Object&gt; m = (Map&lt;String, Object&gt;)JSON.deserializeUntyped(\n                      response.getBody());\n              if (response.getStatusCode() == 200){\n                  results.add(DataSource.UpsertResult.success(\n                          String.valueOf(m.get('id'))));\n              } \n              else {\n                 results.add(DataSource.UpsertResult.failure(\n                         String.valueOf(m.get('id')), \n                         'The callout resulted in an error: ' + \n                         response.getStatusCode()));\n              }\n           } \n           return results;\n       } \n       return null;\n    }\n\/\/ ...<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">deleteRows<\/h2>\n\n\n\n<p>\u8be5\u65b9\u6cd5\u5728\u4ee5\u4e0b\u60c5\u51b5\u4e0b\u8c03\u7528 \u5916\u90e8\u5bf9\u8c61\u8bb0\u5f55\u5c06\u88ab\u5220\u9664\u3002\u60a8\u53ef\u4ee5\u901a\u8fc7\u4ee5\u4e0b\u65b9\u5f0f\u5220\u9664\u5916\u90e8\u5bf9\u8c61\u8bb0\u5f55 Salesforce \u7528\u6237\u754c\u9762\u6216 DML\u3002\u4ee5\u4e0b\u793a\u4f8b\u63d0\u4f9b\u4e86\u4e00\u4e2a\u793a\u4f8b \u65b9\u6cd5\u7684\u5b9e\u73b0\u3002 \u8be5\u793a\u4f8b\u4f7f\u7528\u4f20\u5165\u7684\u8868\u6765\u786e\u5b9a\u9009\u62e9\u4e86\u54ea\u4e2a\u8868\uff0c\u5e76\u4e14\u4ec5\u5f53\u540d\u79f0 \u6240\u9009\u8868\u4e3a \u3002\u5220\u9664\u662f \u5728\u5916\u90e8\u7cfb\u7edf\u4e2d\u4f7f\u7528\u6bcf\u4e2a\u5916\u90e8 ID \u7684\u6807\u6ce8\u6267\u884c\u3002\u586b\u5145 \u4ece\u6807\u6ce8\u54cd\u5e94\u83b7\u5f97\u7684\u7ed3\u679c\u3002\u8bf7\u6ce8\u610f\uff0c\u56e0\u4e3a\u6807\u6ce8\u662f \u9488\u5bf9\u6bcf\u4e2a ID \u521b\u5efa\uff0c\u6b64\u793a\u4f8b\u53ef\u80fd\u4f1a\u8fbe\u5230 Apex \u6807\u6ce8\u9650\u5236\u3002<samp>deleteRows<\/samp><samp>deleteRows<\/samp><samp>DeleteContext<\/samp><samp>Sample<\/samp><samp>DataSource.DeleteResult<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ...\n    global override List&lt;DataSource.DeleteResult&gt; deleteRows(DataSource.DeleteContext \n            context) {\n       if (context.tableSelected == 'Sample'){\n           List&lt;DataSource.DeleteResult&gt; results = new List&lt;DataSource.DeleteResult&gt;();\n           for (String externalId : context.externalIds){\n              HttpResponse response = makeDeleteCallout(externalId);\n              if (response.getStatusCode() == 200){\n                 results.add(DataSource.DeleteResult.success(externalId));\n              } \n              else {\n                 results.add(DataSource.DeleteResult.failure(externalId, \n                         'Callout delete error:' \n                         + response.getBody()));\n              }\n           }\n           return results;\n       }\n       return null;\n     }\n\/\/ ...<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u521b\u5efa\u793a\u4f8b&nbsp;DataSource.Provider&nbsp;\u7c7b<\/h2>\n\n\n\n<p>\u73b0\u5728\uff0c\u60a8\u9700\u8981\u4e00\u4e2a\u7c7b\u6765\u6269\u5c55\u548c\u8986\u76d6 \u4e2d\u7684\u4e00\u4e9b\u65b9\u6cd5\u3002<\/p>\n\n\n\n<p><samp>DataSource.Provider<\/samp><\/p>\n\n\n\n<p>\u60a8\u7684\u73ed\u7ea7\u901a\u77e5 Salesforce \u652f\u6301\u6216\u9700\u8981\u7684\u529f\u80fd\u548c\u8eab\u4efd\u9a8c\u8bc1\u529f\u80fd \u8fde\u63a5\u5230\u5916\u90e8\u7cfb\u7edf\u3002<samp>DataSource.Provider<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>global class SampleDataSourceProvider extends DataSource.Provider {<\/code><\/pre>\n\n\n\n<p>\u5982\u679c\u5916\u90e8\u7cfb\u7edf\u9700\u8981\u8eab\u4efd\u9a8c\u8bc1\uff0cSalesforce \u53ef\u4ee5\u63d0\u4f9b\u8eab\u4efd\u9a8c\u8bc1 \u6765\u81ea\u5916\u90e8\u6570\u636e\u6e90\u5b9a\u4e49\u6216\u7528\u6237\u4e2a\u4eba\u8bbe\u7f6e\u7684\u51ed\u636e\u3002\u4e3a \u4f46\u662f\uff0c\u7b80\u5355\u6765\u8bf4\uff0c\u6b64\u793a\u4f8b\u58f0\u660e\u5916\u90e8\u7cfb\u7edf\u4e0d\u9700\u8981 \u8ba4\u8bc1\u3002\u4e3a\u6b64\uff0c\u5b83\u5c06\u4f5c\u4e3a\u5217\u8868\u4e2d\u7684\u552f\u4e00\u6761\u76ee\u8fd4\u56de \u8eab\u4efd\u9a8c\u8bc1\u529f\u80fd\u3002<samp>AuthenticationCapability.ANONYMOUS<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>override global List&lt;DataSource.AuthenticationCapability&gt;\n        getAuthenticationCapabilities() {\n        List&lt;DataSource.AuthenticationCapability&gt; capabilities =\n            new List&lt;DataSource.AuthenticationCapability&gt;();\n        capabilities.add(\n            DataSource.AuthenticationCapability.ANONYMOUS);\n        return capabilities;\n    }<\/code><\/pre>\n\n\n\n<p>\u6b64\u793a\u4f8b\u8fd8\u58f0\u660e\u5916\u90e8\u7cfb\u7edf\u5141\u8bb8 SOQL \u67e5\u8be2\u3001SOSL \u67e5\u8be2\u3001 Salesforce \u641c\u7d22\u3001\u66f4\u65b0\u63d2\u5165\u6570\u636e\u548c\u5220\u9664\u6570\u636e\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u4e3a\u4e86\u5141\u8bb8 SOQL\uff0c\u8be5\u793a\u4f8b\u58f0\u660e\u4e86\u8be5\u529f\u80fd\u3002<samp>DataSource.Capability.ROW_QUERY<\/samp><\/li>\n\n\n\n<li>\u4e3a\u4e86\u5141\u8bb8 SOSL \u548c Salesforce \u641c\u7d22\uff0c\u8be5\u793a\u4f8b\u58f0\u660e\u4e86\u8be5\u529f\u80fd\u3002<samp>DataSource.Capability.SEARCH<\/samp><\/li>\n\n\n\n<li>\u4e3a\u4e86\u5141\u8bb8\u66f4\u65b0\u63d2\u5165\u5916\u90e8\u6570\u636e\uff0c\u8be5\u793a\u4f8b\u58f0\u660e\u4e86 \u548c \u529f\u80fd\u3002<samp>DataSource.Capability.ROW_CREATE<\/samp><samp>DataSource.Capability.ROW_UPDATE<\/samp><\/li>\n\n\n\n<li>\u4e3a\u4e86\u5141\u8bb8\u5220\u9664\u5916\u90e8\u6570\u636e\uff0c\u8be5\u793a\u4f8b\u58f0\u660e\u4e86\u8be5\u529f\u80fd\u3002<samp>DataSource.Capability.ROW_DELETE<\/samp><\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>override global List&lt;DataSource.Capability&gt; getCapabilities()\n    {\n        List&lt;DataSource.Capability&gt; capabilities = new\n            List&lt;DataSource.Capability&gt;();\n        capabilities.add(DataSource.Capability.ROW_QUERY);\n        capabilities.add(DataSource.Capability.SEARCH);\n        capabilities.add(DataSource.Capability.ROW_CREATE);\n        capabilities.add(DataSource.Capability.ROW_UPDATE);\n        capabilities.add(DataSource.Capability.ROW_DELETE);\n        return capabilities;\n    }<\/code><\/pre>\n\n\n\n<p>\u6700\u540e\uff0c\u8be5\u793a\u4f8b\u6807\u8bc6\u83b7\u53d6\u5916\u90e8\u7cfb\u7edf\u67b6\u6784\u7684\u7c7b\uff0c\u4ee5\u53ca \u5904\u7406\u5916\u90e8\u6570\u636e\u7684\u67e5\u8be2\u548c\u641c\u7d22\u3002<samp>SampleDataSourceConnection<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>override global DataSource.Connection getConnection(\n        DataSource.ConnectionParams connectionParams) {\n        return new SampleDataSourceConnection(connectionParams);\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u8bbe\u7f6e Salesforce Connect \u4ee5\u4f7f\u7528\u60a8\u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/h2>\n\n\n\n<p>\u521b\u5efa \u548c \u7c7b\u540e\uff0cSalesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u5728\u5b89\u88c5\u7a0b\u5e8f\u4e2d\u53ef\u7528\u3002<\/p>\n\n\n\n<p><samp>DataSource.Connection<\/samp><samp>DataSource.Provider<\/samp><\/p>\n\n\n\n<p>\u5b8c\u6210\u201c\u8bbe\u7f6e Salesforce Connect to Access\u201d\u4e2d\u63cf\u8ff0\u7684\u4efb\u52a1 Salesforce \u5e2e\u52a9\u4e2d\u7684\u201c\u4f7f\u7528\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u5916\u90e8\u6570\u636e\u201d\u3002\u8981\u5c06\u5916\u90e8\u5bf9\u8c61\u7684\u5199\u5165\u529f\u80fd\u6dfb\u52a0\u5230\u9002\u914d\u5668\uff0c\u8bf7\u6267\u884c\u4ee5\u4e0b\u64cd\u4f5c\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u4f7f\u6b64\u9002\u914d\u5668\u7684\u5916\u90e8\u6570\u636e\u6e90\u53ef\u5199\u3002\u8bf7\u53c2\u9605\u201c\u5b9a\u4e49 Salesforce \u5e2e\u52a9\u4e2d\u7684 Salesforce Connect &#8211; \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u201d\u3002<\/li>\n\n\n\n<li>\u5b9e\u73b0 \u548c \u65b9\u6cd5 \u9002\u914d\u5668\u3002\u6709\u5173\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605\u8fde\u63a5\u7c7b\u3002<samp>DataSource.Connection.upsertRows()DataSource.Connection.deleteRows()<\/samp><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">\u5173\u4e8e Apex \u8fde\u63a5\u5668\u6846\u67b6\u7684\u5173\u952e\u6982\u5ff5<\/h2>\n\n\n\n<p>\u547d\u540d\u7a7a\u95f4\u63d0\u4f9b\u7c7b \u7528\u4e8e Apex Connector \u6846\u67b6\u3002\u4f7f\u7528 Apex \u8fde\u63a5\u5668\u6846\u67b6\u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668 \u9002\u7528\u4e8e Salesforce Connect\u3002\u7136\u540e\uff0c\u901a\u8fc7 Salesforce \u5c06\u60a8\u7684 Salesforce \u7ec4\u7ec7\u8fde\u63a5\u5230\u4efb\u4f55\u5730\u65b9\u7684\u4efb\u4f55\u6570\u636e \u8fde\u63a5\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002<\/p>\n\n\n\n<p><samp>DataSource<\/samp><\/p>\n\n\n\n<p>\u6211\u4eec\u5efa\u8bae\u60a8\u4e86\u89e3\u4e00\u4e9b\u5173\u952e\u6982\u5ff5\uff0c\u4ee5\u5e2e\u52a9\u60a8\u4f7f\u7528 Apex \u8fde\u63a5\u5668 \u6709\u6548\u7684\u6846\u67b6\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Salesforce Connect \u5916\u90e8\u5bf9\u8c61\u7684\u5916\u90e8 ID \u5f53\u60a8\u4f7f\u7528\u00a0<strong>Salesforce Connect \u7684<\/strong>\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u6570\u636e\u65f6\uff0c\u5916\u90e8\u5bf9\u8c61<br>\u4e0a\u7684\u5916\u90e8 ID \u6807\u51c6\u5b57\u6bb5\u7684\u503c\u6765\u81ea\u547d\u540d\u7684 .<samp>DataSource.ColumnExternalId<\/samp><\/li>\n\n\n\n<li><strong>Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684<\/strong>\u8eab\u4efd\u9a8c\u8bc1<br>\u60a8\u7684\u7c7b\u58f0\u660e\u54ea\u4e9b\u7c7b\u578b\u7684\u51ed\u636e\u53ef\u7528\u4e8e\u5411\u5916\u90e8\u7cfb\u7edf\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1\u3002<samp>DataSource.Provider<\/samp><\/li>\n\n\n\n<li>Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u6807\u6ce8<br>\u5c31\u50cf\u4efb\u4f55\u5176\u4ed6 Apex \u4ee3\u7801\u4e00\u6837\uff0c<strong>Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u8fdb\u884c\u6807\u6ce8<\/strong>\u3002\u5982\u679c\u4e0e\u5916\u90e8\u7cfb\u7edf\u7684\u8fde\u63a5\u9700\u8981\u8eab\u4efd\u9a8c\u8bc1\uff0c\u8bf7\u5c06\u8eab\u4efd\u9a8c\u8bc1\u53c2\u6570\u5408\u5e76\u5230\u6807\u6ce8\u4e2d\u3002<\/li>\n\n\n\n<li><strong>\u4f7f\u7528 Apex \u8fde\u63a5\u5668\u6846\u67b6<\/strong><br>\u8fdb\u884c\u5206\u9875 \u5728\u7528\u6237\u754c\u9762\u4e2d\u663e\u793a\u5927\u91cf\u8bb0\u5f55\u65f6\uff0cSalesforce \u4f1a\u5c06\u8bb0\u5f55\u96c6\u5206\u6210\u591a\u4e2a\u6279\u6b21\u5e76\u663e\u793a\u4e00\u4e2a\u6279\u6b21\u3002\u7136\u540e\uff0c\u60a8\u53ef\u4ee5\u5206\u9875\u6d4f\u89c8\u8fd9\u4e9b\u6279\u6b21\u3002\u4f46\u662f\uff0cSalesforce Connect \u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u4e0d\u4f1a\u81ea\u52a8\u652f\u6301\u4efb\u4f55\u7c7b\u578b\u7684\u5206\u9875\u3002\u82e5\u8981\u652f\u6301\u901a\u8fc7\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u83b7\u53d6\u7684\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u8fdb\u884c\u5206\u9875\uff0c\u8bf7\u5b9e\u73b0\u670d\u52a1\u5668\u9a71\u52a8\u6216\u5ba2\u6237\u7aef\u9a71\u52a8\u7684\u5206\u9875\u3002<\/li>\n\n\n\n<li><strong>queryMore with the Apex Connector Framework<\/strong><br>\u9002\u7528\u4e8e Salesforce Connect \u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u4e0d\u4f1a\u81ea\u52a8\u652f\u6301 API \u67e5\u8be2\u4e2d\u7684\u65b9\u6cd5\u3002\u4f46\u662f\uff0c\u60a8\u7684\u5b9e\u73b0\u5fc5\u987b\u80fd\u591f\u5c06\u5927\u578b\u7ed3\u679c\u96c6\u5206\u89e3\u4e3a\u6279\u5904\u7406\uff0c\u5e76\u4f7f\u7528 SOAP API \u4e2d\u7684\u65b9\u6cd5\u5faa\u73af\u8bbf\u95ee\u5b83\u4eec\u3002\u9ed8\u8ba4\u6279\u5904\u7406\u5927\u5c0f\u4e3a 500 \u6761\u8bb0\u5f55\uff0c\u4f46\u67e5\u8be2\u5f00\u53d1\u4eba\u5458\u53ef\u4ee5\u5728\u67e5\u8be2\u8c03\u7528\u4e2d\u4ee5\u7f16\u7a0b\u65b9\u5f0f\u8c03\u6574\u8be5\u503c\u3002<samp>queryMorequeryMore<\/samp><\/li>\n\n\n\n<li><strong>Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u805a\u5408<\/strong><br>\u5982\u679c\u6536\u5230\u67e5\u8be2\uff0c\u5219\u6240\u9009\u5217\u7684\u5c5e\u6027\u4e2d\u5177\u6709\u8be5\u503c\u3002\u6240\u9009\u5217\u5728 for \u7684\u5c5e\u6027\u4e2d\u63d0\u4f9b\u3002<samp>COUNT()QueryAggregation.COUNTaggregationcolumnsSelectedtableSelectionDataSource.QueryContext<\/samp><\/li>\n\n\n\n<li><strong>Apex \u8fde\u63a5\u5668\u6846\u67b6<\/strong><br>\u4e2d\u7684\u8fc7\u6ee4\u5668\u5305\u542b\u4e00\u4e2a .\u53ef\u4ee5\u6709\u591a\u4e2a .\u6bcf\u4e2a\u5c5e\u6027\u90fd\u6709\u4e00\u4e2a\u5c5e\u6027\uff0c\u8be5\u5c5e\u6027\u8868\u793a SOQL \u6216 SOSL \u67e5\u8be2\u4e2d\u7684\u5b50\u53e5\u3002<samp>DataSource.QueryContextDataSource.TableSelectionDataSource.SearchContextTableSelectionTableSelectionfilterWHERE<\/samp><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Salesforce Connect \u5916\u90e8\u5bf9\u8c61\u7684\u5916\u90e8 ID<\/h2>\n\n\n\n<p>\u5f53\u60a8\u4f7f\u7528 Salesforce Connect \u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u6570\u636e\u65f6\uff0c \u5916\u90e8\u5bf9\u8c61\u4e0a\u7684\u201c\u5916\u90e8 ID\u201d\u6807\u51c6\u5b57\u6bb5\u7684\u503c\u6765\u81ea\u547d\u540d\u7684 .<\/p>\n\n\n\n<p><samp>DataSource.Column<\/samp><samp>ExternalId<\/samp><\/p>\n\n\n\n<p>\u6bcf\u4e2a\u5916\u90e8\u5bf9\u8c61\u90fd\u6709\u4e00\u4e2a\u5916\u90e8 ID&nbsp;\u6807\u51c6\u5b57\u6bb5\u3002\u5b83\u7684\u4ef7\u503c\u89c2 \u552f\u4e00\u6807\u8bc6\u7ec4\u7ec7\u4e2d\u7684\u6bcf\u4e2a\u5916\u90e8\u5bf9\u8c61\u8bb0\u5f55\u3002\u5f53\u5916\u90e8\u5bf9\u8c61\u662f \u5916\u90e8\u67e5\u627e\u5173\u7cfb\u4e2d\u7684\u7236\u9879\uff0c\u5916\u90e8 ID \u6807\u51c6\u5b57\u6bb5\u7528\u4e8e \u6807\u8bc6\u5b50\u8bb0\u5f55\u3002<\/p>\n\n\n\n<p>\u91cd\u8981<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684 Apex \u4ee3\u7801\u5fc5\u987b\u58f0\u660e\u547d\u540d\u5e76\u63d0\u4f9b\u5176\u503c\u3002<samp>DataSource.Column<\/samp><samp>ExternalId<\/samp><\/li>\n\n\n\n<li><a><\/a>\u4e0d\u8981\u4f7f\u7528\u654f\u611f\u6570\u636e\u4f5c\u4e3a \u5916\u90e8 ID \u6807\u51c6\u5b57\u6bb5\u6216\u6307\u5b9a\u4e3a\u540d\u79f0\u5b57\u6bb5\u7684\u5b57\u6bb5\uff0c\u56e0\u4e3a Salesforce \u6709\u65f6\u4f1a\u5b58\u50a8\u8fd9\u4e9b\u503c\u3002<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a><\/a>\u5916\u90e8\u67e5\u627e \u5b50\u8bb0\u5f55\u4e0a\u7684\u5173\u7cfb\u5b57\u6bb5\u5b58\u50a8\u5e76\u663e\u793a\u5916\u90e8 \u7236\u8bb0\u5f55\u7684 ID \u503c\u3002<\/li>\n\n\n\n<li><a><\/a>\u4e3a \u4ec5\u4f9b\u5185\u90e8\u4f7f\u7528\uff0cSalesforce \u5b58\u50a8\u6bcf\u4e2a ID \u7684\u5916\u90e8 ID \u503c \u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u5230\u7684\u884c\u3002&nbsp;<a><\/a>\u6b64\u884c\u4e3a\u4e0d\u4f1a \u5e94\u7528\u4e8e\u4e0e\u9ad8\u6570\u636e\u91cf\u5173\u8054\u7684\u5916\u90e8\u5bf9\u8c61 \u5916\u90e8\u6570\u636e\u6e90\u3002<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u4f8b<\/h2>\n\n\n\n<p>\u793a\u4f8b\u7c7b\u7684\u6458\u5f55\u663e\u793a\u4e86\u540d\u4e3a .<\/p>\n\n\n\n<p><samp>DataSource.Connection<\/samp><samp>DataSource.Column<\/samp><samp>ExternalId<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>override global List&lt;DataSource.Table&gt; sync() {\n        List&lt;DataSource.Table&gt; tables =\n        new List&lt;DataSource.Table&gt;();\n    List&lt;DataSource.Column&gt; columns;\n    columns = new List&lt;DataSource.Column&gt;();\n    columns.add(DataSource.Column.text('title', 255));\n    columns.add(DataSource.Column.text('description',255));\n    columns.add(DataSource.Column.text('createdDate',255));\n    columns.add(DataSource.Column.text('modifiedDate',255));\n    columns.add(DataSource.Column.url('selfLink'));\n    columns.add(DataSource.Column.url('DisplayUrl'));\n    columns.add(DataSource.Column.text('ExternalId',255));\n    tables.add(DataSource.Table.get('googleDrive','title',\n        columns));\n    return tables;\n    }<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u8eab\u4efd\u9a8c\u8bc1<\/h2>\n\n\n\n<p>\u4f60\u7684\u73ed\u7ea7\u58f0\u660e\u4e86\u4ec0\u4e48 \u51ed\u636e\u7c7b\u578b\u53ef\u7528\u4e8e\u5411\u5916\u90e8\u7cfb\u7edf\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1\u3002<\/p>\n\n\n\n<p><samp>DataSource.Provider<\/samp><\/p>\n\n\n\n<p>\u5982\u679c\u00a0<samp>DataSource.Provider<\/samp>\u00a0\u7c7b\u7684\u6269\u5c55\u8fd4\u56de\u00a0<samp>DataSource.AuthenticationCapability<\/samp>\u00a0\u503c\uff0c\u8fd9\u4e9b\u503c\u6307\u793a \u652f\u6301\u8eab\u4efd\u9a8c\u8bc1\uff0c<samp>DataSource.Connection<\/samp>\u00a0\u7c7b\u5728\u6784\u9020\u51fd\u6570\u4e2d\u4f7f\u7528\u00a0<samp>DataSource.ConnectionParams<\/samp>\u00a0\u5b9e\u4f8b\u8fdb\u884c\u5b9e\u4f8b\u5316\u3002\u5b9e\u4f8b\u4e2d\u7684\u8eab\u4efd\u9a8c\u8bc1\u51ed\u636e\u53d6\u51b3\u4e8e\u8eab\u4efd\u00a0Salesforce \u4e2d\u5916\u90e8\u6570\u636e\u6e90\u5b9a\u4e49\u7684 Type \u5b57\u6bb5\u3002<\/p>\n\n\n\n<p><samp>DataSource.ConnectionParams<\/samp><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5982\u679c\u201c\u8eab\u4efd\u7c7b\u578b\u201d\u8bbe\u7f6e\u4e3a \uff0c\u5219\u51ed\u636e\u6765\u81ea\u5916\u90e8\u6570\u636e \u6e90\u5b9a\u4e49\u3002<samp>Named Principal<\/samp><\/li>\n\n\n\n<li>\u5982\u679c\u201c\u8eab\u4efd\u7c7b\u578b\u201d\u8bbe\u7f6e\u4e3a\uff1a<samp>Per User<\/samp>\n<ul class=\"wp-block-list\">\n<li><a><\/a>\u5bf9\u4e8e\u67e5\u8be2\u548c\u641c\u7d22\uff0c \u51ed\u636e\u7279\u5b9a\u4e8e\u8c03\u7528\u67e5\u8be2\u7684\u5f53\u524d\u7528\u6237 \u6216\u641c\u7d22\u3002\u51ed\u636e\u6765\u81ea\u7528\u6237\u7684\u8eab\u4efd\u9a8c\u8bc1 \u5916\u90e8\u7cfb\u7edf\u7684\u8bbe\u7f6e\u3002<\/li>\n\n\n\n<li><a><\/a>\u5bf9\u4e8e\u7ba1\u7406\u8fde\u63a5\uff0c \u4f8b\u5982\u540c\u6b65\u5916\u90e8\u7cfb\u7edf\u7684\u67b6\u6784\uff0c\u51ed\u636e\u5c31\u6765\u4e86 \u4ece\u5916\u90e8\u6570\u636e\u6e90\u5b9a\u4e49\u3002<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u9002\u7528\u4e8e Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684<\/strong><br>OAuth \u5982\u679c\u60a8\u4f7f\u7528 OAuth 2.0 \u8bbf\u95ee\u5916\u90e8\u6570\u636e\uff0c\u8bf7\u4e86\u89e3\u5982\u4f55\u907f\u514d\u8bbf\u95ee\u4ee4\u724c\u8fc7\u671f\u5bfc\u81f4\u7684\u8bbf\u95ee\u4e2d\u65ad\u3002<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684 OAuth<\/h2>\n\n\n\n<p>\u5982\u679c\u60a8\u4f7f\u7528 OAuth 2.0 \u8bbf\u95ee\u5916\u90e8\u6570\u636e\uff0c\u8bf7\u4e86\u89e3\u5982\u4f55\u907f\u514d\u8bbf\u95ee\u4e2d\u65ad \u7531\u8fc7\u671f\u7684\u8bbf\u95ee\u4ee4\u724c\u5bfc\u81f4\u3002\u67d0\u4e9b\u5916\u90e8\u7cfb\u7edf\u4f7f\u7528\u8fc7\u671f\u4e14\u9700\u8981\u5237\u65b0\u7684 OAuth \u8bbf\u95ee\u4ee4\u724c\u3002\u6211\u4eec \u5728\u4ee5\u4e0b\u60c5\u51b5\u4e0b\uff0c\u53ef\u4ee5\u6839\u636e\u9700\u8981\u81ea\u52a8\u5237\u65b0\u8bbf\u95ee\u4ee4\u724c\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7528\u6237\u6216\u5916\u90e8\u6570\u636e\u6e90\u5177\u6709\u6765\u81ea\u5148\u524d OAuth \u7684\u6709\u6548\u5237\u65b0\u4ee4\u724c \u6d41\u3002<\/li>\n\n\n\n<li>\u7c7b\u4e2d\u7684 sync\u3001query \u6216 search \u65b9\u6cd5\u4f1a\u5f15\u53d1 .<samp>DataSource.Connection<\/samp><samp>DataSource.OAuthTokenExpiredException<\/samp><\/li>\n<\/ul>\n\n\n\n<p>\u6211\u4eec\u4f7f\u7528\u7528\u6237\u6216\u5916\u90e8\u6570\u636e\u6e90\u7684\u76f8\u5173 OAuth \u51ed\u636e\u8fdb\u884c\u534f\u5546 \u5e76\u5237\u65b0\u4ee4\u724c\u3002\u8be5\u7c7b\u662f\u4f7f\u7528 \u6211\u4eec\u63d0\u4f9b\u7684 \u5230\u6784\u9020\u51fd\u6570\u3002\u7136\u540e\u91cd\u65b0\u8c03\u7528\u641c\u7d22\u6216\u67e5\u8be2\u3002<samp>DataSource.Connection<\/samp><samp>DataSource.ConnectionParams<\/samp><\/p>\n\n\n\n<p>\u5982\u679c\u8eab\u4efd\u9a8c\u8bc1\u63d0\u4f9b\u7a0b\u5e8f\u672a\u63d0\u4f9b\u5237\u65b0\u4ee4\u724c\uff0c\u5219\u8bbf\u95ee\u5916\u90e8 \u5f53\u5f53\u524d\u8bbf\u95ee\u4ee4\u724c\u8fc7\u671f\u65f6\uff0c\u7cfb\u7edf\u5c06\u4e22\u5931\u3002\u5982\u679c\u51fa\u73b0\u8b66\u544a\u6d88\u606f \u5916\u90e8\u6570\u636e\u6e90\u8be6\u7ec6\u4fe1\u606f\u9875\u9762\uff0c\u8bf7\u54a8\u8be2\u60a8\u7684 OAuth \u63d0\u4f9b\u5546\u4ee5\u83b7\u53d6\u6709\u5173\u4ee5\u4e0b\u5185\u5bb9\u7684\u4fe1\u606f \u8bf7\u6c42\u8131\u673a\u8bbf\u95ee\u6216\u5237\u65b0\u4ee4\u724c\u3002<\/p>\n\n\n\n<p>\u5bf9\u4e8e\u67d0\u4e9b\u8eab\u4efd\u9a8c\u8bc1\u63d0\u4f9b\u7a0b\u5e8f\uff0c\u8bf7\u6c42\u8131\u673a\u8bbf\u95ee\u5c31\u50cf\u6dfb\u52a0 \u8303\u56f4\u3002\u4f8b\u5982\uff0c\u8981\u4ece Salesforce \u8eab\u4efd\u9a8c\u8bc1\u63d0\u4f9b\u5546\u8bf7\u6c42\u8131\u673a\u8bbf\u95ee\uff0c \u6dfb\u52a0\u5230 Salesforce \u7ec4\u7ec7\u4e2d\u8eab\u4efd\u9a8c\u8bc1\u63d0\u4f9b\u5546\u5b9a\u4e49\u7684\u9ed8\u8ba4\u8303\u56f4\u5b57\u6bb5\u3002<kbd>refresh_token<\/kbd><\/p>\n\n\n\n<p>\u5bf9\u4e8e\u5176\u4ed6\u8eab\u4efd\u9a8c\u8bc1\u63d0\u4f9b\u7a0b\u5e8f\uff0c\u5fc5\u987b\u5728\u8eab\u4efd\u9a8c\u8bc1\u4e2d\u8bf7\u6c42\u8131\u673a\u8bbf\u95ee URL \u4f5c\u4e3a\u67e5\u8be2\u53c2\u6570\u3002\u4f8b\u5982\uff0c\u4f7f\u7528 Google \u65f6\uff0c\u5c06&nbsp;Append \u9644\u52a0\u5230 Authorize Endpoint&nbsp;Salesforce \u4e2d\u8eab\u4efd\u9a8c\u8bc1\u63d0\u4f9b\u5546\u5b9a\u4e49\u4e0a\u7684 URL \u5b57\u6bb5 \u7ec4\u7ec7\u3002\u82e5\u8981\u7f16\u8f91\u6388\u6743\u7ec8\u7ed3\u70b9\uff0c\u8bf7\u9009\u62e9<strong>\u201c\u6253\u5f00 ID\u201d&nbsp;<\/strong>\u5728&nbsp;Provider Type&nbsp;\u5b57\u6bb5\u4e2d\u8fde\u63a5 \u8eab\u4efd\u9a8c\u8bc1\u63d0\u4f9b\u7a0b\u5e8f\u3002\u6709\u5173\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605\u201c\u914d\u7f6e OpenID Connect \u8eab\u4efd\u9a8c\u8bc1\u201d \u63d0\u4f9b\u5546\u201d\u3002<kbd>?access_type=offline<\/kbd><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u6807\u6ce8<\/h2>\n\n\n\n<p>\u5c31\u50cf\u4efb\u4f55\u5176\u4ed6 Apex \u4ee3\u7801\u4e00\u6837\uff0cSalesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u8fdb\u884c\u6807\u6ce8\u3002 \u5982\u679c\u4e0e\u5916\u90e8\u7cfb\u7edf\u7684\u8fde\u63a5\u9700\u8981\u8eab\u4efd\u9a8c\u8bc1\uff0c\u8bf7\u5408\u5e76 \u8eab\u4efd\u9a8c\u8bc1\u53c2\u6570\u6dfb\u52a0\u5230\u6807\u6ce8\u4e2d\u3002<\/p>\n\n\n\n<p>\u8eab\u4efd\u9a8c\u8bc1\u53c2\u6570\u5c01\u88c5\u5728\u5bf9\u8c61\u4e2d\uff0c\u5e76\u63d0\u4f9b\u7ed9\u7c7b\u7684\u6784\u9020\u51fd\u6570\u3002<samp>ConnectionParams<\/samp><samp>DataSource.Connection<\/samp>\u4f8b\u5982\uff0c\u5982\u679c\u60a8\u7684\u8fde\u63a5\u9700\u8981 OAuth \u8bbf\u95ee\u4ee4\u724c\uff0c\u8bf7\u4f7f\u7528\u7c7b\u4f3c\u4e8e \u4ee5\u540e\u3002<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public HttpResponse getResponse(String url) {\n    Http httpProtocol = new Http();\n    HttpRequest request = new HttpRequest();\n    request.setEndPoint(url);\n    request.setMethod('GET');\n    request.setHeader('Authorization', 'Bearer ' + \n            this.connectionInfo.oauthToken);\n    HttpResponse response = httpProtocol.send(request);\n    return response;\n}<\/code><\/pre>\n\n\n\n<p>\u5982\u679c\u60a8\u7684\u8fde\u63a5\u9700\u8981\u57fa\u672c\u5bc6\u7801\u8eab\u4efd\u9a8c\u8bc1\uff0c\u8bf7\u4f7f\u7528\u7c7b\u4f3c\u4e8e \u4ee5\u540e\u3002<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public HttpResponse getResponse(String url) {\n    Http httpProtocol = new Http();\n    HttpRequest request = new HttpRequest();\n    request.setEndPoint(url);\n    request.setMethod('GET');\n    string encodedHeaderValue = EncodingUtil.base64Encode(Blob.valueOf(\n            this.connectioninfo.username + ':' + \n            this.connectionInfo.password));\n    request.setHeader('Authorization', 'Basic ' + encodedHeaderValue);\n    HttpResponse response = httpProtocol.send(request);\n    return response;\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u547d\u540d\u51ed\u636e\u4f5c\u4e3a Salesforce Connect Custom \u7684\u6807\u6ce8\u7aef\u70b9 \u9002\u914d\u5668<\/h2>\n\n\n\n<p>Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u83b7\u53d6\u5b58\u50a8\u7684\u76f8\u5173\u51ed\u636e \u5728 Salesforce \u4e2d\uff0c\u53ea\u8981\u6709\u9700\u8981\u3002\u4f46\u662f\uff0c\u60a8\u7684 Apex \u4ee3\u7801\u5fc5\u987b\u5e94\u7528\u8fd9\u4e9b \u6240\u6709\u6807\u6ce8\u7684\u51ed\u636e\uff0c\u4f46\u5c06\u547d\u540d\u51ed\u636e\u6307\u5b9a\u4e3a \u6807\u6ce8\u7aef\u70b9\u3002\u547d\u540d\u51ed\u636e\u5141\u8bb8 Salesforce \u5904\u7406\u8eab\u4efd\u9a8c\u8bc1 \u903b\u8f91\uff0c\u8fd9\u6837\u4f60\u7684\u4ee3\u7801\u5c31\u4e0d\u5fc5\u8fd9\u6837\u505a\u4e86\u3002<\/p>\n\n\n\n<p>\u5982\u679c\u6240\u6709\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u6807\u6ce8\u90fd\u4f7f\u7528\u547d\u540d\u51ed\u636e\uff0c\u5219\u53ef\u4ee5\u8bbe\u7f6e\u5916\u90e8 \u6570\u636e\u6e90\u7684\u201c\u8eab\u4efd\u9a8c\u8bc1\u534f\u8bae\u201d\u5b57\u6bb5\u8bbe\u7f6e\u4e3a<strong>\u201c\u5426\u201d \u8eab\u4efd\u9a8c\u8bc1<\/strong>\u3002\u547d\u540d\u51ed\u636e\u4f1a\u6dfb\u52a0\u76f8\u5e94\u7684 \u8bc1\u4e66\uff0c\u5e76\u4e14\u53ef\u4ee5\u5c06\u6807\u51c6\u6388\u6743\u6807\u5934\u6dfb\u52a0\u5230\u6807\u6ce8\u4e2d\u3002\u4f60\u4e5f \u4e0d\u9700\u8981\u4e3a\u5b9a\u4e49\u4e3a \u547d\u540d\u51ed\u636e\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u4f7f\u7528 Apex \u8fde\u63a5\u5668\u6846\u67b6\u8fdb\u884c\u5206\u9875<\/h2>\n\n\n\n<p>\u5728\u7528\u6237\u754c\u9762\u4e2d\u663e\u793a\u5927\u91cf\u8bb0\u5f55\u65f6\uff0cSalesforce \u4f1a\u7834\u574f \u8bbe\u7f6e\u4e3a\u6279\u6b21\u5e76\u663e\u793a\u4e00\u4e2a\u6279\u6b21\u3002\u7136\u540e\uff0c\u60a8\u53ef\u4ee5\u5206\u9875\u6d4f\u89c8\u8fd9\u4e9b\u6279\u6b21\u3002\u7136\u800c Salesforce Connect \u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u4e0d\u4f1a\u81ea\u52a8\u652f\u6301\u4efb\u4f55\u7c7b\u578b\u7684\u5206\u9875\u3002\u81ea \u652f\u6301\u901a\u8fc7\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u83b7\u53d6\u7684\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u8fdb\u884c\u5206\u9875\uff0c\u5b9e\u73b0 \u670d\u52a1\u5668\u9a71\u52a8\u6216\u5ba2\u6237\u7aef\u9a71\u52a8\u7684\u5206\u9875\u3002<\/p>\n\n\n\n<p>\u4f7f\u7528\u670d\u52a1\u5668\u9a71\u52a8\u7684\u5206\u9875\uff0c\u5916\u90e8\u7cfb\u7edf\u63a7\u5236\u5206\u9875\u5e76\u5ffd\u7565\u4efb\u4f55\u6279\u5904\u7406 \u5728\u67e5\u8be2\u4e2d\u6307\u5b9a\u7684\u8fb9\u754c\u6216\u9875\u9762\u5927\u5c0f\u3002\u82e5\u8981\u542f\u7528\u670d\u52a1\u5668\u9a71\u52a8\u7684\u5206\u9875\uff0c\u8bf7\u6267\u884c\u4ee5\u4e0b\u64cd\u4f5c\uff1a \u5728\u7c7b\u4e2d\u58f0\u660e\u529f\u80fd\u3002 \u6b64\u5916\uff0c\u60a8\u7684 Apex \u4ee3\u7801\u5fc5\u987b\u751f\u6210\u4e00\u4e2a\u67e5\u8be2\u4ee4\u724c\uff0c\u5e76\u4f7f\u7528\u5b83\u6765\u786e\u5b9a\u548c\u83b7\u53d6 \u4e0b\u4e00\u6279\u7ed3\u679c\u3002<samp>QUERY_PAGINATION_SERVER_DRIVEN<\/samp><samp>DataSource.Provider<\/samp><\/p>\n\n\n\n<p>\u4f7f\u7528\u5ba2\u6237\u7aef\u9a71\u52a8\u7684\u5206\u9875\uff0c\u53ef\u4ee5\u4f7f\u7528 and \u5b50\u53e5\u5bf9\u7ed3\u679c\u96c6\u8fdb\u884c\u5206\u9875\u3002\u56e0\u7d20 \u548c\u5c5e\u6027\u6765\u786e\u5b9a\u8981\u8fd4\u56de\u7684\u884c\u3002\u4f8b\u5982 \u5047\u8bbe\u7ed3\u679c\u96c6\u6709 20 \u884c\uff0c\u6570\u503c\u4ecb\u4e8e 1 \u5230 20 \u4e4b\u95f4\u3002\u5982\u679c\u6211\u4eec\u8981\u6c42 of \u548c of \uff0c\u6211\u4eec\u5e0c\u671b\u5f97\u5230\u5e26\u6709 ID \u2013 \u7684\u884c\u3002\u6211\u4eec\u5efa\u8bae\u60a8\u6267\u884c\u6240\u6709\u64cd\u4f5c \u5728\u5916\u90e8\u7cfb\u7edf\u4e2d\uff0c\u5728 Apex \u4e4b\u5916\uff0c\u4f7f\u7528\u5916\u90e8\u65b9\u6cd5\u8fdb\u884c\u8fc7\u6ee4 \u7cfb\u7edf\u652f\u6301\u3002<samp>LIMIT<\/samp><samp>OFFSET<\/samp><samp>offset<\/samp><samp>maxResults<\/samp><samp>DataSource.QueryContext<\/samp><samp>ExternalID<\/samp><samp>offset<\/samp><samp>5<\/samp><samp>maxResults<\/samp><samp>5<\/samp><samp>6<\/samp><samp>10<\/samp><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">queryMore&nbsp;\u4e0e Apex \u8fde\u63a5\u5668\u6846\u67b6<\/h2>\n\n\n\n<p>Salesforce Connect \u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u4e0d\u4f1a\u81ea\u52a8\u652f\u6301 API \u67e5\u8be2\u4e2d\u7684\u65b9\u6cd5\u3002\u4f46\u662f\uff0c\u60a8\u7684 \u5b9e\u73b0\u5fc5\u987b\u80fd\u591f\u5c06\u5927\u578b\u7ed3\u679c\u96c6\u5206\u89e3\u4e3a\u591a\u4e2a\u6279\u6b21\u5e76\u5bf9\u5176\u8fdb\u884c\u8fed\u4ee3 \u901a\u8fc7\u4f7f\u7528 SOAP API \u4e2d\u7684\u65b9\u6cd5\u3002\u8fd9 \u9ed8\u8ba4\u6279\u5927\u5c0f\u4e3a 500 \u6761\u8bb0\u5f55\uff0c\u4f46\u67e5\u8be2\u5f00\u53d1\u4eba\u5458\u53ef\u4ee5\u8c03\u6574\u8be5\u503c \u5728\u67e5\u8be2\u8c03\u7528\u4e2d\u4ee5\u7f16\u7a0b\u65b9\u5f0f\u3002<\/p>\n\n\n\n<p><samp>queryMore<\/samp><samp>queryMore<\/samp>\u8981\u652f\u6301 \uff0c\u60a8\u7684\u5b9e\u73b0\u5fc5\u987b \u6307\u793a\u5b58\u5728\u7684\u6570\u636e\u662f\u5426\u591a\u4e8e\u5f53\u524d\u6279\u5904\u7406\u4e2d\u7684\u6570\u636e\u3002\u5f53\u95ea\u7535 \u5e73\u53f0\u77e5\u9053\u5b58\u5728\u66f4\u591a\u6570\u636e\uff0c\u60a8\u7684API\u67e5\u8be2\u5c06\u8fd4\u56de\u4e00\u4e2a\u7c7b\u4f3c\u4e8e \u4ee5\u540e\u3002<\/p>\n\n\n\n<p><samp>queryMore<\/samp><samp>QueryResult<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n         \"totalSize\" =&gt; -1,\n              \"done\" =&gt; false,\n    \"nextRecordsUrl\" =&gt; \"\/services\/data\/v32.0\/query\/01gxx000000B5OgAAK-2000\",\n           \"records\" =&gt; &#91;\n        &#91;   0] {\n            \"attributes\" =&gt; {\n                \"type\" =&gt; \"Sample__x\",\n                 \"url\" =&gt; \n                     \"\/services\/data\/v32.0\/sobjects\/Sample__x\/x06xx0000000001AAA\"\n            },\n            \"ExternalId\" =&gt; \"id0\"\n        },\n        &#91;   1] {\n            \"attributes\" =&gt; {\n                \"type\" =&gt; \"Sample__x\",\n                 \"url\" =&gt; \n                     \"\/services\/data\/v32.0\/sobjects\/Sample__x\/x06xx0000000002AAA\"\n            },\n\u2026\n}<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u4f7f\u7528\u670d\u52a1\u5668\u9a71\u52a8\u7684\u5206\u9875<\/strong><br>\u652f\u6301 queryMore \u4f7f\u7528\u670d\u52a1\u5668\u9a71\u52a8\u7684\u5206\u9875\u65f6\uff0c\u5916\u90e8\u7cfb\u7edf\u63a7\u5236\u5206\u9875\u5e76\u5ffd\u7565\u67e5\u8be2\u4e2d\u6307\u5b9a\u7684\u4efb\u4f55\u6279\u5904\u7406\u8fb9\u754c\u6216\u9875\u9762\u5927\u5c0f\u3002\u82e5\u8981\u542f\u7528\u670d\u52a1\u5668\u9a71\u52a8\u7684\u5206\u9875\uff0c\u8bf7\u5728\u7c7b\u4e2d\u58f0\u660e\u8be5\u529f\u80fd\u3002<samp>QUERY_PAGINATION_SERVER_DRIVENDataSource.Provider<\/samp><\/li>\n\n\n\n<li><strong>\u901a\u8fc7\u4f7f\u7528\u5ba2\u6237\u7aef\u9a71\u52a8\u7684\u5206\u9875<\/strong><br>\u652f\u6301 queryMore \u5728\u5ba2\u6237\u7aef\u9a71\u52a8\u7684\u5206\u9875\u4e2d\uff0c\u53ef\u4ee5\u4f7f\u7528 and \u5b50\u53e5\u5bf9\u7ed3\u679c\u96c6\u8fdb\u884c\u5206\u9875\u3002<samp>LIMITOFFSET<\/samp><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u652f\u6301\u67e5\u8be2More&nbsp;by Using \u670d\u52a1\u5668\u9a71\u52a8\u7684\u5bfb\u547c<\/h2>\n\n\n\n<p>\u4f7f\u7528\u670d\u52a1\u5668\u9a71\u52a8\u7684\u5206\u9875\u65f6\uff0c\u5916\u90e8\u7cfb\u7edf\u63a7\u5236\u5206\u9875\u5e76\u5ffd\u7565\u4efb\u4f55 \u67e5\u8be2\u4e2d\u6307\u5b9a\u7684\u6279\u5904\u7406\u8fb9\u754c\u6216\u9875\u9762\u5927\u5c0f\u3002\u542f\u7528\u670d\u52a1\u5668\u9a71\u52a8 \u5206\u9875\uff0c\u5728\u7c7b\u4e2d\u58f0\u660e\u529f\u80fd\u3002<\/p>\n\n\n\n<p><samp>QUERY_PAGINATION_SERVER_DRIVEN<\/samp><samp>DataSource.Provider<\/samp><\/p>\n\n\n\n<p>\u5f53\u8fd4\u56de\u7684\u6ca1\u6709 \u5305\u542b\u6574\u4e2a\u7ed3\u679c\u96c6\uff0c\u5fc5\u987b\u63d0\u4f9b\u4e00\u4e2a\u503c\u3002\u67e5\u8be2 token \u662f\u6211\u4eec\u4e34\u65f6\u5b58\u50a8\u7684\u4efb\u610f\u5b57\u7b26\u4e32\u3002\u5f53\u6211\u4eec\u8981\u6c42\u4e0b\u4e00\u6279\u65f6 \u4e2d\uff0c\u6211\u4eec\u5c06\u67e5\u8be2\u4ee4\u724c\u4f20\u9012\u56de .\u60a8\u7684 Apex \u4ee3\u7801\u5fc5\u987b\u4f7f\u7528 \u8be5\u67e5\u8be2\u4ee4\u724c\uff0c\u7528\u4e8e\u786e\u5b9a\u54ea\u4e9b\u884c\u5c5e\u4e8e\u4e0b\u4e00\u6279\u7ed3\u679c\u3002<samp>DataSource.TableResult<\/samp><samp>TableResult<\/samp><samp>queryMoreToken<\/samp><samp>DataSource.QueryContext<\/samp><\/p>\n\n\n\n<p>\u5f53\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u8fd4\u56de\u6700\u7ec8\u6279\u5904\u7406\u65f6\uff0c\u5b83\u4e0d\u5f97\u8fd4\u56de \u4e2d\u7684\u503c\u3002<samp>queryMoreToken<\/samp><samp>TableResult<\/samp><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u4f7f\u7528\u5ba2\u6237\u7aef\u9a71\u52a8\u652f\u6301&nbsp;queryMore&nbsp;\u5bfb\u547c<\/h2>\n\n\n\n<p>\u4f7f\u7528\u5ba2\u6237\u7aef\u9a71\u52a8\u7684\u5206\u9875\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528 and \u5b50\u53e5\u5bf9\u7ed3\u679c\u8fdb\u884c\u5206\u9875 \u96c6\u3002<\/p>\n\n\n\n<p><samp>LIMIT<\/samp><samp>OFFSET<\/samp><\/p>\n\n\n\n<p>\u5982\u679c\u5916\u90e8\u7cfb\u7edf\u53ef\u4ee5\u8fd4\u56de\u6bcf\u4e2a\u67e5\u8be2\u7684\u7ed3\u679c\u96c6\u7684\u603b\u5927\u5c0f\uff0c \u5728\u7c7b\u4e2d\u58f0\u660e\u529f\u80fd\u3002\u786e\u4fdd \u6bcf\u4e2a\u641c\u7d22\u6216\u67e5\u8be2\u90fd\u8fd4\u56de\u503c \u5728\u3002\u5982\u679c\u603b\u5927\u5c0f \u5927\u4e8e\u6279\u5904\u7406\u4e2d\u8fd4\u56de\u7684\u884c\u6570\uff0c\u6211\u4eec\u751f\u6210\u4e00\u4e2a\u94fe\u63a5\u5e76\u5c06\u6807\u5fd7\u8bbe\u7f6e\u4e3a .\u6211\u4eec\u8fd8\u5c06 \u5230\u4f60\u7684\u503c \u4f9b\u5e94\u3002<samp>QUERY_TOTAL_SIZE<\/samp><samp>DataSource.Provider<\/samp><samp>totalSize<\/samp><samp>DataSource.TableResult<\/samp><samp>nextRecordsUrl<\/samp><samp>done<\/samp><samp>false<\/samp><samp>totalSize<\/samp><samp>TableResult<\/samp><\/p>\n\n\n\n<p>\u5982\u679c\u5916\u90e8\u7cfb\u7edf\u65e0\u6cd5\u8fd4\u56de\u6bcf\u4e2a\u67e5\u8be2\u7684\u603b\u5927\u5c0f\uff0c\u8bf7\u4e0d\u8981\u5728\u7c7b\u4e2d\u58f0\u660e\u8be5\u529f\u80fd\u3002\u6bcf\u5f53\u6211\u4eec\u8fdb\u884c\u67e5\u8be2\u65f6 \u901a\u8fc7\u60a8\u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\uff0c\u6211\u4eec\u8981\u6c42\u589e\u52a0\u4e00\u884c\u3002\u4f8b\u5982\uff0c\u5982\u679c\u60a8\u8fd0\u884c\u67e5\u8be2\uff0c\u6211\u4eec\u8c03\u7528 \u5177\u6709 \u5c5e\u6027\u8bbe\u7f6e\u4e3a 6 \u7684\u5bf9\u8c61\u4e0a\u7684\u65b9\u6cd5\u3002\u5b58\u5728\u6216 \u7ed3\u679c\u96c6\u4e2d\u7f3a\u5c11\u7b2c\u516d\u884c\u8868\u793a\u662f\u5426\u6709\u66f4\u591a\u6570\u636e\u53ef\u7528\u3002\u6211\u4eec \u4f46\u662f\uff0c\u5047\u8bbe\u6211\u4eec\u67e5\u8be2\u7684\u6570\u636e\u96c6\u5728\u67e5\u8be2\u4e4b\u95f4\u4e0d\u4f1a\u66f4\u6539\u3002\u5982\u679c \u6570\u636e\u96c6\u5728\u67e5\u8be2\u4e4b\u95f4\u53d1\u751f\u53d8\u5316\uff0c\u60a8\u53ef\u80fd\u4f1a\u770b\u5230\u91cd\u590d\u7684\u884c\u6216\u65e0\u6cd5\u83b7\u53d6\u6240\u6709\u884c \u7ed3\u679c\u3002<samp>QUERY_TOTAL_SIZE<\/samp><samp>DataSource.Provider<\/samp><samp>SELECT ExternalId FROM Sample LIMIT 5<\/samp><samp>query<\/samp><samp>DataSource.Connection<\/samp><samp>DataSource.QueryContext<\/samp><samp>maxResults<\/samp><\/p>\n\n\n\n<p>\u5f52\u6839\u7ed3\u5e95\uff0c\u5f53\u60a8\u68c0\u7d22\u5c0f\u6570\u636e\u65f6\uff0c\u8bbf\u95ee\u5916\u90e8\u6570\u636e\u6700\u6709\u6548 \u6570\u636e\u91cf\u548c\u60a8\u67e5\u8be2\u7684\u6570\u636e\u96c6\u5f88\u5c11\u66f4\u6539\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Salesforce Connect \u81ea\u5b9a\u4e49\u9002\u914d\u5668\u7684\u805a\u5408<\/h2>\n\n\n\n<p>\u5982\u679c\u60a8\u6536\u5230\u67e5\u8be2\uff0c\u5219\u9009\u4e2d \u5217\u5728\u5176\u5c5e\u6027\u4e2d\u5177\u6709\u503c\u3002\u6240\u9009\u5217\u662f \u5728 for \u7684\u5c5e\u6027\u4e2d\u63d0\u4f9b\u3002<\/p>\n\n\n\n<p><samp>COUNT()<\/samp><samp>QueryAggregation.COUNT<\/samp><samp>aggregation<\/samp><samp>columnsSelected<\/samp><samp>tableSelection<\/samp><samp>DataSource.QueryContext<\/samp><\/p>\n\n\n\n<p>\u4e0b\u9762\u7684\u793a\u4f8b\u6f14\u793a\u5982\u4f55\u5e94\u7528\u5c5e\u6027\u7684\u503c\u6765\u5904\u7406\u67e5\u8be2\u3002<samp>aggregation<\/samp><samp>COUNT()<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Handle COUNT() queries\nif (context.tableSelection.columnsSelected.size() == 1 &amp;&amp;      \n    context.tableSelection.columnsSelected.get(0).aggregation == \n        QueryAggregation.COUNT) {\n    List&lt;Map&lt;String, Object&gt;&gt; countResponse = new List&lt;Map&lt;String, Object&gt;&gt;();\n    Map&lt;String, Object&gt; countRow = new Map&lt;String, Object&gt;();\n    countRow.put(context.tableSelection.columnsSelected.get(0).columnName, \n    response.size());\n    countResponse.add(countRow);\n    return countResponse;\n}<\/code><\/pre>\n\n\n\n<p>\u805a\u5408\u67e5\u8be2\u4ecd\u7136\u53ef\u4ee5\u5177\u6709\u7b5b\u9009\u5668\uff0c\u56e0\u6b64\u67e5\u8be2\u65b9\u6cd5\u53ef\u4ee5\u5b9e\u73b0\u5982\u4e0b\u65b9\u5f0f \u4ee5\u4e0b\u793a\u4f8b\u652f\u6301\u57fa\u672c\u67e5\u8be2\uff0c\u65e0\u8bba\u662f\u5426\u4f7f\u7528\u7b5b\u9009\u5668\u3002<samp>aggregation<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>override global DataSource.TableResult query(DataSource.QueryContext context) {\n    List&lt;Map&lt;String,Object&gt;&gt; rows = retrieveData(context);\n    List&lt;Map&lt;String,Object&gt;&gt; response = postFilterRecords(\n            context.tableSelection.filter, rows);\n    if (context.tableSelection.columnsSelected.size() == 1 &amp;&amp;        \n        context.tableSelection.columnsSelected.get(0).aggregation ==   \n                DataSource.QueryAggregation.COUNT) {\n        List&lt;Map&lt;String, Object&gt;&gt; countResponse = new List&lt;Map&lt;String, \n                Object&gt;&gt;();\n        Map&lt;String, Object&gt; countRow = new Map&lt;String, Object&gt;();\n        countRow.put(context.tableSelection.columnsSelected.get(0).columnName, \n                response.size());\n        countResponse.add(countRow);\n        return DataSource.TableResult.get(context, countResponse);\n    }\n    return DataSource.TableResult.get(context, response);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Apex \u8fde\u63a5\u5668\u6846\u67b6\u4e2d\u7684\u8fc7\u6ee4\u5668<\/h2>\n\n\n\n<p>\u5305\u542b\u4e00\u4e2a .\u53ef\u4ee5\u6709\u591a\u4e2a .\u6bcf\u4e2a\u90fd\u6709\u4e00\u4e2a\u5c5e\u6027 \u8868\u793a SOQL \u6216 SOSL \u4e2d\u7684\u5b50\u53e5 \u67e5\u8be2\u3002<\/p>\n\n\n\n<p><samp>DataSource.QueryContext<\/samp><samp>DataSource.TableSelection<\/samp><samp>DataSource.SearchContext<\/samp><samp>TableSelection<\/samp><samp>TableSelection<\/samp><samp>filter<\/samp><samp>WHERE<\/samp><\/p>\n\n\n\n<p>\u4f8b\u5982\uff0c\u5f53\u7528\u6237\u8f6c\u5230\u5916\u90e8\u5bf9\u8c61\u7684\u8bb0\u5f55\u8be6\u7ec6\u4fe1\u606f\u9875\u9762\u65f6\uff0c\u5c06\u6267\u884c your \u3002\u80cc\u540e \u573a\u666f\u4e2d\uff0c\u6211\u4eec\u751f\u6210\u4e00\u4e2a\u7c7b\u4f3c\u4e8e\u4ee5\u4e0b\u5185\u5bb9\u7684 SOQL \u67e5\u8be2\u3002<samp>DataSource.Connection<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SELECT columnNames \nFROM externalObjectApiName \nWHERE ExternalId = 'selectedExternalObjectExternalId'<\/code><\/pre>\n\n\n\n<p>\u6b64 SOQL \u67e5\u8be2\u4f1a\u5bfc\u81f4\u8c03\u7528\u7c7b\u4e0a\u7684\u65b9\u6cd5\u3002 \u4ee5\u4e0b\u4ee3\u7801\u53ef\u4ee5\u68c0\u6d4b\u5230\u8fd9\u79cd\u60c5\u51b5\u3002<samp>query<\/samp><samp>DataSource.Connection<\/samp><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (context.tableSelection.filter != null) {\n    if (context.tableSelection.filter.type == DataSource.FilterType.EQUALS \n        &amp;&amp; 'ExternalId' ==  context.tableSelection.filter.columnName \n        &amp;&amp; context.tableSelection.filter.columnValue instanceOf String) {\n        String selection = (String)context.tableSelection.filter.columnValue;\n        return DataSource.TableResult.get(true, null, \n                tableSelection.tableSelected, findSingleResult(selection));\n    }\n}<\/code><\/pre>\n\n\n\n<p>\u6b64\u4ee3\u7801\u793a\u4f8b\u5047\u5b9a\u60a8\u5b9e\u73b0\u4e86\u4e00\u4e2a\u8fd4\u56de\u5355\u4e2a\u8bb0\u5f55\u7684\u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u7ed9\u5b9a\u9009\u5b9a\u7684 .\u786e\u4fdd\u60a8\u7684\u4ee3\u7801\u83b7\u53d6 \u4e0e\u8bf7\u6c42\u7684 .<samp>findSingleResult<\/samp><samp>ExternalId<\/samp><samp>ExternalId<\/samp><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u5728 Apex \u8fde\u63a5\u5668\u6846\u67b6<\/strong><br>\u4e2d\u8bc4\u4f30\u8fc7\u6ee4\u5668 \u5982\u679c\u67d0\u884c\u4e0e\u8fc7\u6ee4\u5668\u63cf\u8ff0\u7684\u6761\u4ef6\u5339\u914d\uff0c\u5219\u8fc7\u6ee4\u5668\u7684\u8ba1\u7b97\u7ed3\u679c\u4e3a true\u3002<\/li>\n\n\n\n<li><strong>Apex \u8fde\u63a5\u5668\u6846\u67b6<\/strong><br>\u7b5b\u9009\u5668\u4e2d\u7684\u590d\u5408\u7b5b\u9009\u5668\u53ef\u4ee5\u5177\u6709\u5b50\u7b5b\u9009\u5668\uff0c\u8fd9\u4e9b\u5b50\u7b5b\u9009\u5668\u5b58\u50a8\u5728\u5c5e\u6027\u4e2d\u3002<samp>subfilters<\/samp><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u8bc4\u4f30 Apex \u8fde\u63a5\u5668\u6846\u67b6\u4e2d\u7684\u8fc7\u6ee4\u5668<\/h2>\n\n\n\n<p>\u5982\u679c\u67d0\u884c\u4e0e\u4ee5\u4e0b\u6761\u4ef6\u5339\u914d\uff0c\u5219\u7b5b\u9009\u5668\u5c06\u8be5\u884c\u7684\u8ba1\u7b97\u7ed3\u679c\u4e3a true\uff1a \u8fc7\u6ee4\u5668\u63cf\u8ff0\u3002<\/p>\n\n\n\n<p>\u4f8b\u5982\uff0c\u5047\u8bbe a \u5df2\u8bbe\u7f6e\u4e3a \u3001 \u548c \u8bbe\u7f6e\u4e3a \u3002\u5c06\u8fd4\u56de\u8fdc\u7a0b\u8868\u4e2d\u5217\u6761\u76ee\u7b49\u4e8e 42 \u7684\u4efb\u4f55\u884c\u3002<samp>DataSource.Filter<\/samp><samp>columnName<\/samp><samp>meaningOfLife<\/samp><samp>columnValue<\/samp><samp>42<\/samp><samp>type<\/samp><samp>EQUALS<\/samp><samp>meaningOfLife<\/samp><\/p>\n\n\n\n<p>\u76f8\u53cd\uff0c\u5047\u8bbe\u7b5b\u9009\u5668\u5df2\u8bbe\u7f6e\u4e3a \u3001 \u548c \u8bbe\u7f6e\u4e3a \u3002\u6211\u4eec\u5c06\u6784\u9020\u4e00\u4e2a\u5bf9\u8c61\uff0c\u5176\u4e2d\u5305\u542b\u503c\u5c0f\u4e8e 3 \u7684\u6240\u6709\u884c\u3002<samp>type<\/samp><samp>LESS_THAN<\/samp><samp>columnValue<\/samp><samp>3<\/samp><samp>columnName<\/samp><samp>numericCol<\/samp><samp>DataSource.TableResult<\/samp><samp>numericCol<\/samp><\/p>\n\n\n\n<p>\u82e5\u8981\u63d0\u9ad8\u6027\u80fd\uff0c\u8bf7\u5728\u5916\u90e8\u7cfb\u7edf\u4e2d\u6267\u884c\u6240\u6709\u7b5b\u9009\u3002\u4f60\u53ef\u4ee5\uff0c\u4e3a\u4e86 \u4f8b\u5982\uff0c\u5c06\u5bf9\u8c61\u8f6c\u6362\u4e3a SQL \u6216 OData \u67e5\u8be2\uff0c\u6216\u5c06\u5176\u6620\u5c04\u5230 SOAP \u67e5\u8be2\u4e0a\u7684\u53c2\u6570\u3002\u5982\u679c\u5916\u90e8\u7cfb\u7edf\u8fd4\u56de \u5927\u91cf\u6570\u636e\uff0c\u60a8\u5728 Apex \u4ee3\u7801\u4e2d\u8fdb\u884c\u8fc7\u6ee4\uff0c\u60a8\u5f88\u5feb\u5c31\u4f1a\u8d85\u8fc7 \u8c03\u901f\u5668\u9650\u5236\u3002<samp>Filter<\/samp><\/p>\n\n\n\n<p>\u5982\u679c\u65e0\u6cd5\u5728\u5916\u90e8\u7cfb\u7edf\u4e2d\u6267\u884c\u6240\u6709\u8fc7\u6ee4\uff0c\u8bf7\u5c3d\u53ef\u80fd\u591a\u5730\u5728\u90a3\u91cc\u6267\u884c \u5e76\u8fd4\u56de\u5c3d\u53ef\u80fd\u5c11\u7684\u6570\u636e\u3002\u7136\u540e\u7b5b\u9009\u8f83\u5c0f\u7684\u6570\u636e\u96c6\u5408 \u60a8\u7684 Apex \u4ee3\u7801\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Apex \u8fde\u63a5\u5668\u6846\u67b6\u4e2d\u7684\u590d\u5408\u8fc7\u6ee4\u5668<\/h2>\n\n\n\n<p>\u7b5b\u9009\u5668\u53ef\u4ee5\u5177\u6709\u5b50\u7b5b\u9009\u5668\uff0c\u8fd9\u4e9b\u5b50\u7b5b\u9009\u5668\u5b58\u50a8\u5728\u5c5e\u6027\u4e2d\u3002<\/p>\n\n\n\n<p><samp>subfilters<\/samp><\/p>\n\n\n\n<p>\u5982\u679c\u7b5b\u9009\u5668\u5177\u6709\u5b50\u9879\uff0c\u5219\u7b5b\u9009\u5668\u5fc5\u987b \u4ee5\u4e0b\u5176\u4e2d\u4e00\u9879\u3002<samp>type<\/samp><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>\u8fc7\u6ee4\u5668\u7c7b\u578b<\/th><th>\u63cf\u8ff0<\/th><\/tr><\/thead><tbody><tr><td><samp>AND_<\/samp><\/td><td>\u6211\u4eec\u8fd4\u56de\u4e0e<em>\u6240\u6709<\/em>\u5b50\u7b5b\u9009\u5668\u5339\u914d\u7684\u6240\u6709\u884c\u3002<\/td><\/tr><tr><td><samp>OR_<\/samp><\/td><td>\u6211\u4eec\u8fd4\u56de\u4e0e<em>\u4efb\u4f55<\/em>\u5b50\u7b5b\u9009\u5668\u5339\u914d\u7684\u6240\u6709\u884c\u3002<\/td><\/tr><tr><td><samp>NOT_<\/samp><\/td><td>\u7b5b\u9009\u5668\u53cd\u8f6c\u5176\u5b50\u7b5b\u9009\u5668\u8ba1\u7b97\u884c\u7684\u65b9\u5f0f\u3002\u8fc7\u6ee4\u5668 \u6b64\u7c7b\u578b\u53ea\u80fd\u6709\u4e00\u4e2a\u5b50\u7b5b\u9009\u5668\u3002<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>\u6b64\u4ee3\u7801\u793a\u4f8b\u6f14\u793a\u5982\u4f55\u5904\u7406\u590d\u5408\u7b5b\u9009\u5668\u3002<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>override global DataSource.TableResult query(DataSource.QueryContext context) {\n    \/\/ Call out to an external data source and retrieve a set of records.\n    \/\/ We should attempt to get as much information as possible about the \n    \/\/ query from the QueryContext, to minimize the number of records \n    \/\/ that we return.\n    List&lt;Map&lt;String,Object&gt;&gt; rows = retrieveData(context);\n    \n    \/\/ This only filters the results. Anything in the query that we don\u2019t \n    \/\/ currently support, such as aggregation or sorting, is ignored.\n    return DataSource.TableResult.get(context, postFilterRecords(\n        context.tableSelection.filter, rows));\n}\n\nprivate List&lt;Map&lt;String,Object&gt;&gt; retrieveData(DataSource.QueryContext context) {\n    \/\/ Call out to an external data source. Form the callout so that\n    \/\/ it filters as much as possible on the remote site,\n    \/\/ based on the parameters in the QueryContext.\n    return ...;\n}\n\nprivate List&lt;Map&lt;String,Object&gt;&gt; postFilterRecords(\n    DataSource.Filter filter, List&lt;Map&lt;String,Object&gt;&gt; rows) {\n    if (filter == null) {\n        return rows;\n    }\n    DataSource.FilterType type = filter.type;\n    List&lt;Map&lt;String,Object&gt;&gt; retainedRows = new List&lt;Map&lt;String,Object&gt;&gt;();\n    if (type == DataSource.FilterType.NOT_) {\n        \/\/ We expect one Filter in the subfilters.\n        DataSource.Filter subfilter = filter.subfilters.get(0);\n        for (Map&lt;String,Object&gt; row : rows) {\n            if (!evaluate(filter, row)) {\n                retainedRows.add(row);\n            }\n        }\n        return retainedRows;\n    } else if (type == DataSource.FilterType.AND_) {\n        \/\/ For each filter, find all matches; anything that matches ALL filters \n        \/\/ is returned.\n        retainedRows = rows;\n        for (DataSource.Filter subfilter : filter.subfilters) {\n            retainedRows = postFilterRecords(subfilter, retainedRows);\n        }\n        return retainedRows;\n    } else if (type == DataSource.FilterType.OR_) {\n        \/\/ For each filter, find all matches. Anything that matches \n        \/\/ at least one filter is returned.\n        for (DataSource.Filter subfilter : filter.subfilters) {\n            List&lt;Map&lt;String,Object&gt;&gt; matchedRows = postFilterRecords(\n                subfilter, rows);\n            retainedRows.addAll(matchedRows);\n        }\n        return retainedRows;\n    } else {\n        \/\/ Find all matches for this filter in our collection of records.\n        for (Map&lt;String,Object&gt; row : rows) {\n            if (evaluate(filter, row)) {\n                retainedRows.add(row);\n            }\n        }\n        return retainedRows;\n    }\n}\n\nprivate Boolean evaluate(DataSource.Filter filter, Map&lt;String,Object&gt; row) {\n    if (filter.type == DataSource.FilterType.EQUALS) {\n        String columnName = filter.columnName;\n        Object expectedValue = filter.columnValue;\n        Object foundValue = row.get(columnName);\n        return expectedValue.equals(foundValue);\n    } else {\n        \/\/ Throw an exception; implementing other filter types is left\n        \/\/ as an exercise for the reader.\n        throwException('Unexpected filter type: ' + filter.type);\n    }\n    return false;\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Apex \u8fde\u63a5\u5668\u6846\u67b6\u7684\u6ce8\u610f\u4e8b\u9879<\/h2>\n\n\n\n<p>\u4e86\u89e3\u521b\u5efa Salesforce Connect \u81ea\u5b9a\u4e49\u7684\u9650\u5236\u548c\u6ce8\u610f\u4e8b\u9879 \u5177\u6709 Apex \u8fde\u63a5\u5668\u6846\u67b6\u7684\u9002\u914d\u5668\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a><\/a>\u5982\u679c\u66f4\u6539\u5e76\u4fdd\u5b58\u7c7b\uff0c\u8bf7\u91cd\u65b0\u4fdd\u5b58 \u76f8\u5e94\u7684\u7c7b\u3002 \u5426\u5219\uff0c\u5728\u5b9a\u4e49\u5916\u90e8\u6570\u636e\u6e90\u65f6\uff0c\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u4e0d\u4f1a \u663e\u793a\u4e3a\u201c\u7c7b\u578b\u201d\u5b57\u6bb5\u7684\u9009\u9879\u3002<samp>DataSource.Connection<\/samp><samp>DataSource.Provider<\/samp>\u6b64\u5916\uff0c \u5173\u8054\u7684\u5916\u90e8\u5bf9\u8c61\u7684\u81ea\u5b9a\u4e49\u9009\u9879\u5361\u4e0d\u518d\u663e\u793a\u5728 Salesforce UI \u4e2d\u3002<\/li>\n\n\n\n<li>\u5305\u542b\u81ea\u5b9a\u4e49\u7684 Apex \u4ee3\u7801\u4e2d\u4e0d\u5141\u8bb8 DML \u64cd\u4f5c \u9002\u914d\u5668\u3002<\/li>\n\n\n\n<li>\u786e\u4fdd\u60a8\u4e86\u89e3\u5916\u90e8\u7cfb\u7edf API \u7684\u9650\u5236\u3002\u4f8b\u5982 \u67d0\u4e9b\u5916\u90e8\u7cfb\u7edf\u4ec5\u63a5\u53d7\u6700\u591a 40 \u884c\u7684\u8bf7\u6c42\u3002<\/li>\n\n\n\n<li>Apex \u6570\u636e\u7c7b\u578b\u9650\u5236\uff1a\n<ul class=\"wp-block-list\">\n<li>\u53cc\u7cbe\u5ea6 &#8211; \u8d85\u8fc7 18 \u4f4d\u6709\u6548\u6570\u5b57\u65f6\uff0c\u8be5\u503c\u5c06\u5931\u53bb\u7cbe\u5ea6\u3002\u4e3a \u7cbe\u5ea6\u66f4\u9ad8\uff0c\u4f7f\u7528\u5c0f\u6570\u800c\u4e0d\u662f\u53cc\u7cbe\u5ea6\u3002<\/li>\n\n\n\n<li>\u5b57\u7b26\u4e32 &#8211; \u5982\u679c\u957f\u5ea6\u5927\u4e8e 255 \u4e2a\u5b57\u7b26\uff0c\u5219\u5b57\u7b26\u4e32\u4e3a \u6620\u5c04\u5230 Salesforce \u4e2d\u7684\u957f\u6587\u672c\u533a\u57df\u5b57\u6bb5\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Salesforce Connect \u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53d7\u5230\u4e0e\u4efb\u4f55\u9002\u914d\u5668\u76f8\u540c\u7684\u9650\u5236 \u5176\u4ed6 Apex \u4ee3\u7801\u3002\u4f8b\u5982\uff1a\n<ul class=\"wp-block-list\">\n<li>\u6240\u6709 Apex \u8c03\u901f\u5668\u9650\u5236\u5747\u9002\u7528\u3002<\/li>\n\n\n\n<li>\u6d4b\u8bd5\u65b9\u6cd5\u4e0d\u652f\u6301 Web \u670d\u52a1\u6807\u6ce8\u3002\u6267\u884c Web \u7684\u6d4b\u8bd5 \u670d\u52a1\u6807\u6ce8\u5931\u8d25\u3002\u5bf9\u4e8e\u6f14\u793a\u5982\u4f55\u907f\u514d\u8fd9\u4e9b\u5931\u8d25\u7684\u793a\u4f8b \u901a\u8fc7\u8fd4\u56de\u6a21\u62df\u54cd\u5e94\u8fdb\u884c\u6d4b\u8bd5\uff0c\u8bf7\u53c2\u9605\u9002\u7528\u4e8e Salesforce \u7684 Google \u4e91\u7aef\u786c\u76d8\u2122\u81ea\u5b9a\u4e49\u9002\u914d\u5668 \u8fde\u63a5\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><a><\/a>\u5728 Apex \u6d4b\u8bd5\u4e2d\uff0c\u4f7f\u7528\u52a8\u6001 SOQL \u8fdb\u884c\u67e5\u8be2 \u5916\u90e8\u5bf9\u8c61\u3002\u5bf9\u5916\u90e8\u5bf9\u8c61\u6267\u884c\u9759\u6001 SOQL \u67e5\u8be2\u7684\u6d4b\u8bd5 \u5931\u8d25\u3002<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Apex \u8fde\u63a5\u5668\u6846\u67b6\u793a\u4f8b<\/h2>\n\n\n\n<p>\u8fd9\u4e9b\u793a\u4f8b\u8bf4\u660e\u4e86\u5982\u4f55\u4f7f\u7528 Apex Connector Framework \u521b\u5efa\u81ea\u5b9a\u4e49 Salesforce Connect \u7684\u9002\u914d\u5668\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u9002\u7528\u4e8e Salesforce Connect \u7684 Google Drive \u81ea\u5b9a\u4e49\u9002\u914d\u5668 \u6b64\u793a\u4f8b\u8bf4\u660e\u5982\u4f55\u4f7f\u7528\u6807\u6ce8\u548c OAuth \u8fde\u63a5\u5230\u5916\u90e8\u7cfb\u7edf\uff0c\u5728\u672c\u4f8b\u4e2d\u4e3a Google Drive\u2122<\/strong>\u2122 \u5728\u7ebf\u5b58\u50a8\u670d\u52a1\u3002<br>\u8be5\u793a\u4f8b\u8fd8\u6f14\u793a\u4e86\u5982\u4f55\u901a\u8fc7\u8fd4\u56de\u6d4b\u8bd5\u65b9\u6cd5\u7684\u6a21\u62df\u54cd\u5e94\u6765\u907f\u514d Web \u670d\u52a1\u6807\u6ce8\u7684\u6d4b\u8bd5\u5931\u8d25\u3002<\/li>\n\n\n\n<li><strong>\u9002\u7528\u4e8e Salesforce Connect \u7684 Google \u56fe\u4e66\u81ea\u5b9a\u4e49\u9002\u914d\u5668 \u6b64\u793a\u4f8b\u8bf4\u660e\u4e86\u5982\u4f55\u89e3\u51b3\u5916\u90e8\u7cfb\u7edf API \u7684\u8981\u6c42\u548c\u9650\u5236\uff1a\u5728\u672c\u4f8b\u4e2d\u4e3a Google \u56fe\u4e66\u2122<\/strong>\u00a0API \u7cfb\u5217\u3002<\/li>\n\n\n\n<li><strong>Salesforce Connect \u7684\u73af\u56de\u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/strong>\u00a0\u6b64\u793a\u4f8b\u8bf4\u660e\u5982\u4f55\u5904\u7406\u67e5\u8be2\u4e2d\u7684\u7b5b\u9009\u3002<br>\u4e3a\u7b80\u5355\u8d77\u89c1\uff0c\u6b64\u793a\u4f8b\u5c06 Salesforce \u7ec4\u7ec7\u4f5c\u4e3a\u5916\u90e8\u7cfb\u7edf\u8fde\u63a5\u5230\u81ea\u8eab\u3002<\/li>\n\n\n\n<li><strong>\u9002\u7528\u4e8e Salesforce Connect \u7684 GitHub \u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/strong>\u00a0\u6b64\u793a\u4f8b\u8bf4\u660e\u5982\u4f55\u652f\u6301\u95f4\u63a5\u67e5\u627e\u5173\u7cfb\u3002<br>\u95f4\u63a5\u67e5\u627e\u5173\u7cfb\u5c06\u5b50\u5916\u90e8\u5bf9\u8c61\u94fe\u63a5\u5230\u7236\u6807\u51c6\u5bf9\u8c61\u6216\u81ea\u5b9a\u4e49\u5bf9\u8c61\u3002<\/li>\n\n\n\n<li><strong>\u9002\u7528\u4e8e Salesforce Connect \u7684 Stack Overflow \u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/strong>\u00a0\u6b64\u793a\u4f8b\u8bf4\u660e\u5982\u4f55\u652f\u6301\u5916\u90e8\u67e5\u627e\u5173\u7cfb\u548c\u591a\u4e2a\u8868\u3002<br>\u5916\u90e8\u67e5\u627e\u5173\u7cfb\u5c06\u5b50\u6807\u51c6\u5bf9\u8c61\u3001\u81ea\u5b9a\u4e49\u5bf9\u8c61\u6216\u5916\u90e8\u5bf9\u8c61\u94fe\u63a5\u5230\u7236\u5916\u90e8\u5bf9\u8c61\u3002\u6bcf\u4e2a\u8868\u90fd\u53ef\u4ee5\u6210\u4e3a Salesforce \u7ec4\u7ec7\u4e2d\u7684\u5916\u90e8\u5bf9\u8c61\u3002<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u9002\u7528\u4e8e Salesforce Connect \u7684 Google Drive\u2122 \u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/h2>\n\n\n\n<p>\u6b64\u793a\u4f8b\u8bf4\u660e\u5982\u4f55\u4f7f\u7528\u6807\u6ce8\u548c OAuth \u8fde\u63a5\u5230\u5916\u90e8 \u7cfb\u7edf\uff0c\u5728\u672c\u4f8b\u4e2d\u4e3a Google Drive\u2122 \u5728\u7ebf\u5b58\u50a8\u670d\u52a1\u3002\u8be5\u793a\u4f8b\u8fd8 \u6f14\u793a\u5982\u4f55\u901a\u8fc7\u8fd4\u56de Web \u670d\u52a1\u6807\u6ce8\u7684\u6a21\u62df\u54cd\u5e94\u6765\u907f\u514d\u6d4b\u8bd5\u5931\u8d25 \u6d4b\u8bd5\u65b9\u6cd5\u3002<\/p>\n\n\n\n<p>\u8981\u4f7f\u6b64\u793a\u4f8b\u53ef\u9760\u5730\u5de5\u4f5c\uff0c\u8bf7\u5728\u8bbe\u7f6e OAuth \u65f6\u8bf7\u6c42\u8131\u673a\u8bbf\u95ee\uff0c\u4ee5\u4fbf Salesforce \u53ef\u4ee5\u83b7\u53d6\u5e76\u7ef4\u62a4\u8fde\u63a5\u7684\u5237\u65b0\u4ee4\u724c\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">DriveDataSourceConnection \u7c7b<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Extends the DataSource.Connection class to enable \n *   Salesforce to sync the external system\u2019s schema \n *   and to handle queries and searches of the external data. \n **\/\nglobal class DriveDataSourceConnection extends\n    DataSource.Connection {\n    private DataSource.ConnectionParams connectionInfo;\n    \n    \/**\n     *   Constructor for DriveDataSourceConnection.\n     **\/\n    global DriveDataSourceConnection(\n        DataSource.ConnectionParams connectionInfo) {\n        this.connectionInfo = connectionInfo;\n    }\n    \n    \/**\n     *   Called when an external object needs to get a list of \n     *   schema from the external data source, for example when \n     *   the administrator clicks \u201cValidate and Sync\u201d in the \n     *   user interface for the external data source.   \n     **\/\n    override global List&lt;DataSource.Table&gt; sync() {\n        List&lt;DataSource.Table&gt; tables =\n            new List&lt;DataSource.Table&gt;();\n        List&lt;DataSource.Column&gt; columns;\n        columns = new List&lt;DataSource.Column&gt;();\n        columns.add(DataSource.Column.text('title', 255));\n        columns.add(DataSource.Column.text('description',255));\n        columns.add(DataSource.Column.text('createdDate',255));\n        columns.add(DataSource.Column.text('modifiedDate',255));\n        columns.add(DataSource.Column.url('selfLink'));\n        columns.add(DataSource.Column.url('DisplayUrl'));\n        columns.add(DataSource.Column.text('ExternalId',255));\n        tables.add(DataSource.Table.get('googleDrive','title',\n            columns));\n        return tables;\n    }\n\n    \/**\n     *   Called to query and get results from the external \n     *   system for SOQL queries, list views, and detail pages \n     *   for an external object that\u2019s associated with the \n     *   external data source.\n     *   \n     *   The QueryContext argument represents the query to run \n     *   against a table in the external system.\n     *   \n     *   Returns a list of rows as the query results.\n     **\/\n    override global DataSource.TableResult query(\n        DataSource.QueryContext context) {\n        DataSource.Filter filter = context.tableSelection.filter;\n        String url;\n        if (filter != null) {\n            String thisColumnName = filter.columnName;\n            if (thisColumnName != null &amp;&amp; \n                    thisColumnName.equals('ExternalId'))\n                url = 'https:\/\/www.googleapis.com\/drive\/v2\/'\n                + 'files\/' + filter.columnValue;\n            else\n                url = 'https:\/\/www.googleapis.com\/drive\/v2\/'\n                + 'files';\n        } else {\n            url = 'https:\/\/www.googleapis.com\/drive\/v2\/' \n            + 'files';\n        }\n\n        \/**\n         * Filters, sorts, and applies limit and offset clauses.\n         **\/\n        List&lt;Map&lt;String, Object&gt;&gt; rows = \n            DataSource.QueryUtils.process(context, getData(url));\n        return DataSource.TableResult.get(true, null,\n            context.tableSelection.tableSelected, rows);\n    }\n\n    \/**\n     *   Called to do a full text search and get results from\n     *   the external system for SOSL queries and Salesforce\n     *   global searches.\n     *   \n     *   The SearchContext argument represents the query to run \n     *   against a table in the external system.\n     *   \n     *   Returns results for each table that the SearchContext \n     *   requested to be searched.\n     **\/\n    override global List&lt;DataSource.TableResult&gt; search(\n        DataSource.SearchContext context) {\n        List&lt;DataSource.TableResult&gt; results =\n            new List&lt;DataSource.TableResult&gt;();\n\n        for (Integer i =0;i&lt; context.tableSelections.size();i++) {\n            String entity = context.tableSelections&#91;i].tableSelected;\n            String url = \n                'https:\/\/www.googleapis.com\/drive\/v2\/files'+\n                '?q=fullText+contains+\\''+context.searchPhrase+'\\'';\n            results.add(DataSource.TableResult.get(\n                true, null, entity, getData(url)));\n        }\n\n        return results;\n    }\n\n    \/**\n     *   Helper method to parse the data.\n     *   The url argument is the URL of the external system.\n     *   Returns a list of rows from the external system.\n     **\/\n    public List&lt;Map&lt;String, Object&gt;&gt; getData(String url) {\n        String response = getResponse(url);\n\n        List&lt;Map&lt;String, Object&gt;&gt; rows =\n            new List&lt;Map&lt;String, Object&gt;&gt;();\n\n        Map&lt;String, Object&gt; responseBodyMap = (Map&lt;String, Object&gt;)\n            JSON.deserializeUntyped(response);\n\n        \/**\n         *   Checks errors.\n         **\/\n        Map&lt;String, Object&gt; error =\n            (Map&lt;String, Object&gt;)responseBodyMap.get('error');\n        if (error!=null) {\n            List&lt;Object&gt; errorsList =\n                (List&lt;Object&gt;)error.get('errors');\n            Map&lt;String, Object&gt; errors =\n                (Map&lt;String, Object&gt;)errorsList&#91;0];\n            String errorMessage = (String)errors.get('message');\n            throw new DataSource.OAuthTokenExpiredException(errorMessage);\n        }\n\n        List&lt;Object&gt; fileItems=(List&lt;Object&gt;)responseBodyMap.get('items');\n        if (fileItems != null) {\n            for (Integer i=0; i &lt; fileItems.size(); i++) {\n                Map&lt;String, Object&gt; item = \n                    (Map&lt;String, Object&gt;)fileItems&#91;i];\n                rows.add(createRow(item));  \n            }\n        } else {\n            rows.add(createRow(responseBodyMap));\n        }\n\n        return rows;\n    }\n\n    \/**\n     *   Helper method to populate the External ID and Display \n     *   URL fields on external object records based on the 'id' \n     *   value that\u2019s sent by the external system.\n     *   \n     *   The Map&lt;String, Object&gt; item parameter maps to the data \n     *   that represents a row.\n     *   \n     *   Returns an updated map with the External ID and \n     *   Display URL values.\n     **\/\n    public Map&lt;String, Object&gt; createRow(\n        Map&lt;String, Object&gt; item){\n        Map&lt;String, Object&gt; row = new Map&lt;String, Object&gt;();\n        for ( String key : item.keySet() ) {\n            if (key == 'id') {\n                row.put('ExternalId', item.get(key));\n            } else if (key=='selfLink') {\n                row.put(key, item.get(key));\n                row.put('DisplayUrl', item.get(key));\n            } else {\n                row.put(key, item.get(key));\n            }\n        }\n        return row;\n    }\n    \n    static String mockResponse = '{' +\n      '  \"kind\": \"drive#file\",' +\n      '  \"id\": \"12345\",' +\n      '  \"selfLink\": \"files\/12345\",' +\n      '  \"title\": \"Mock File\",' +\n      '  \"mimeType\": \"application\/text\",' +\n      '  \"description\": \"Mock response that\u2019s used during tests\",' +\n      '  \"createdDate\": \"2016-04-20\",' +\n      '  \"modifiedDate\": \"2016-04-20\",' +\n      '  \"version\": 1' +\n      '}';\n    \n    \/**\n     *   Helper method to make the HTTP GET call.\n     *   The url argument is the URL of the external system.\n     *   Returns the response from the external system.\n     **\/\n    public String getResponse(String url) {\n        if (System.Test.isRunningTest()) {\n          \/\/ Avoid callouts during tests. Return mock data instead.\n          return mockResponse;\n        } else {\n          \/\/ Perform callouts for production (non-test) results.\n          Http httpProtocol = new Http();\n          HttpRequest request = new HttpRequest();\n          request.setEndPoint(url);\n          request.setMethod('GET');\n          request.setHeader('Authorization', 'Bearer '+\n              this.connectionInfo.oauthToken);\n          HttpResponse response = httpProtocol.send(request);\n          return response.getBody();\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">DriveDataSourceProvider Class<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Extends the DataSource.Provider base class to create a \n *   custom adapter for Salesforce Connect. The class informs \n *   Salesforce of the functional and authentication \n *   capabilities that are supported by or required to connect \n *   to an external system.\n **\/\nglobal class DriveDataSourceProvider\n    extends DataSource.Provider {\n \n    \/**\n     *   Declares the types of authentication that can be used \n     *   to access the external system.\n     **\/\n    override global List&lt;DataSource.AuthenticationCapability&gt;\n        getAuthenticationCapabilities() {\n        List&lt;DataSource.AuthenticationCapability&gt; capabilities =\n            new List&lt;DataSource.AuthenticationCapability&gt;();\n        capabilities.add(\n            DataSource.AuthenticationCapability.OAUTH);\n        capabilities.add(\n            DataSource.AuthenticationCapability.ANONYMOUS);\n        return capabilities;\n    }\n \n    \/**\n     *   Declares the functional capabilities that the \n     *   external system supports.\n     **\/\n    override global List&lt;DataSource.Capability&gt;\n        getCapabilities() {\n        List&lt;DataSource.Capability&gt; capabilities =\n            new List&lt;DataSource.Capability&gt;();\n        capabilities.add(DataSource.Capability.ROW_QUERY);\n        capabilities.add(DataSource.Capability.SEARCH);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the associated DataSource.Connection class.\n     **\/\n    override global DataSource.Connection getConnection(\n        DataSource.ConnectionParams connectionParams) {\n        return new DriveDataSourceConnection(connectionParams);\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u9002\u7528\u4e8e Salesforce Connect \u7684 Google \u56fe\u4e66\u2122\u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/h2>\n\n\n\n<p>\u6b64\u793a\u4f8b\u8bf4\u660e\u5982\u4f55\u89e3\u51b3 \u5916\u90e8\u7cfb\u7edf\u7684 API\uff1a\u5728\u672c\u4f8b\u4e2d\u4e3a Google \u56fe\u4e66 API \u7cfb\u5217\u3002\u4e3a\u4e86\u4e0e Google \u56fe\u4e66\u2122\u670d\u52a1\u96c6\u6210\uff0c\u6211\u4eec\u6309\u5982\u4e0b\u65b9\u5f0f\u8bbe\u7f6e Salesforce Connect\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Google Books API \u6700\u591a\u5141\u8bb8\u8fd4\u56de 40 \u4e2a\u7ed3\u679c\uff0c\u56e0\u6b64\u6211\u4eec\u5f00\u53d1\u4e86 \u7528\u4e8e\u5904\u7406\u8d85\u8fc7 40 \u884c\u7684\u7ed3\u679c\u96c6\u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002<\/li>\n\n\n\n<li>Google Books API \u53ea\u80fd\u6309\u641c\u7d22\u76f8\u5173\u6027\u548c\u53d1\u5e03\u65e5\u671f\u8fdb\u884c\u6392\u5e8f\uff0c\u56e0\u6b64\u6211\u4eec \u5f00\u53d1\u6211\u4eec\u7684\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u4ee5\u7981\u7528\u5217\u6392\u5e8f\u3002<\/li>\n\n\n\n<li>\u4e3a\u4e86\u652f\u6301 OAuth\uff0c\u6211\u4eec\u5728 Salesforce \u4e2d\u8bbe\u7f6e\u4e86\u8eab\u4efd\u9a8c\u8bc1\u8bbe\u7f6e\uff0c\u4ee5\u4fbf \u8bf7\u6c42\u7684\u8bbf\u95ee\u4ee4\u724c\u6743\u9650\u8303\u56f4\u5305\u62ec \u3002<kbd>https:\/\/www.googleapis.com\/auth\/books<\/kbd><\/li>\n\n\n\n<li>\u4e3a\u4e86\u5141\u8bb8 Apex \u6807\u6ce8\uff0c\u6211\u4eec\u5728 Salesforce \u4e2d\u5b9a\u4e49\u4e86\u4ee5\u4e0b\u8fdc\u7a0b\u7ad9\u70b9\uff1a\n<ul class=\"wp-block-list\">\n<li>https:\/\/www.googleapis.com<\/li>\n\n\n\n<li>https:\/\/books.google.com<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">BooksDataSourceConnection \u7c7b<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Extends the DataSource.Connection class to enable\n *   Salesforce to sync the external system metadata\n *   schema and to handle queries and searches of the external\n *   data.\n **\/\nglobal class BooksDataSourceConnection extends\n    DataSource.Connection {\n \n    private DataSource.ConnectionParams connectionInfo;\n\n    \/\/ Constructor for BooksDataSourceConnection.\n    global BooksDataSourceConnection(DataSource.ConnectionParams\n                                    connectionInfo) {\n        this.connectionInfo = connectionInfo;\n    }\n\n    \/**\n     *   Called when an external object needs to get a list of \n     *   schema from the external data source, for example when \n     *   the administrator clicks \u201cValidate and Sync\u201d in the \n     *   user interface for the external data source.   \n     **\/\n    override global List&lt;DataSource.Table&gt; sync() {\n        List&lt;DataSource.Table&gt; tables =\n            new List&lt;DataSource.Table&gt;();\n        List&lt;DataSource.Column&gt; columns;\n        columns = new List&lt;DataSource.Column&gt;();\n        columns.add(getColumn('title'));\n        columns.add(getColumn('description'));\n        columns.add(getColumn('publishedDate'));\n        columns.add(getColumn('publisher'));\n        columns.add(DataSource.Column.url('DisplayUrl'));\n        columns.add(DataSource.Column.text('ExternalId', 255));\n        tables.add(DataSource.Table.get('googleBooks', 'title',\n                                        columns));\n        return tables;\n    }\n\n    \/**\n     *   Google Books API v1 doesn't support sorting,\n     *   so we create a column with sortable = false.\n     **\/\n    private DataSource.Column getColumn(String columnName) {\n        DataSource.Column column = DataSource.Column.text(columnName,\n                                                        255);\n        column.sortable = false;\n        return column;\n    }\n\n    \/**\n     *   Called to query and get results from the external\n     *   system for SOQL queries, list views, and detail pages\n     *   for an external object that's associated with the\n     *   external data source.\n     *\n     *   The QueryContext argument represents the query to run\n     *   against a table in the external system.\n     *\n     *   Returns a list of rows as the query results.\n     **\/\n    override global DataSource.TableResult query(\n                    DataSource.QueryContext contexts) {\n        DataSource.Filter filter = contexts.tableSelection.filter;\n        String url;\n        if (contexts.tableSelection.columnsSelected.size() == 1 &amp;&amp;\n        contexts.tableSelection.columnsSelected.get(0).aggregation ==\n            DataSource.QueryAggregation.COUNT) {\n            return getCount(contexts);\n        }\n\n        if (filter != null) {\n            String thisColumnName = filter.columnName;\n            if (thisColumnName != null &amp;&amp;\n                thisColumnName.equals('ExternalId')) {\n                url = 'https:\/\/www.googleapis.com\/books\/v1\/' +\n                    'volumes?q=' + filter.columnValue +\n                    '&amp;maxResults=1&amp;id=' + filter.columnValue;\n                return DataSource.TableResult.get(true, null,\n                            contexts.tableSelection.tableSelected,\n                            getData(url));\n            }\n            else {\n                url = 'https:\/\/www.googleapis.com\/books\/' +\n                    'v1\/volumes?q=' + filter.columnValue +\n                    '&amp;id=' + filter.columnValue +\n                    '&amp;maxResults=40' + '&amp;startIndex=';\n            }\n        } else {\n            url = 'https:\/\/www.googleapis.com\/books\/v1\/' +\n                'volumes?q=america&amp;' + '&amp;maxResults=40' +\n                '&amp;startIndex=';\n        }\n        \/**\n         *   Google Books API v1 supports maxResults of 40\n         *   so we handle pagination explicitly in the else statement\n         *   when we handle more than 40 records per query.\n         **\/\n        if (contexts.maxResults &lt; 40) {\n            return DataSource.TableResult.get(true, null,\n                    contexts.tableSelection.tableSelected,\n                    getData(url + contexts.offset));\n        }\n        else {\n            return fetchData(contexts, url);\n        }\n     }\n\n    \/**\n     *   Helper method to fetch results when maxResults is \n     *   greater than 40 (the max value for maxResults supported \n     *   by Google Books API v1).\n     **\/\n    private DataSource.TableResult fetchData(\n        DataSource.QueryContext contexts, String url) {\n        Integer fetchSlot = (contexts.maxResults \/ 40) + 1;\n        List&lt;Map&lt;String, Object&gt;&gt; data =\n            new List&lt;Map&lt;String, Object&gt;&gt;();\n        Integer startIndex = contexts.offset;\n        for(Integer count = 0; count &lt; fetchSlot; count++) {\n            data.addAll(getData(url + startIndex));\n            if(count == 0)\n                contexts.offset = 41;\n            else\n                contexts.offset += 40;\n        }\n \n        return DataSource.TableResult.get(true, null,\n                        contexts.tableSelection.tableSelected, data);\n    }\n\n    \/**\n     *   Helper method to execute count() query.\n     **\/\n    private DataSource.TableResult getCount(\n        DataSource.QueryContext contexts) {\n        String url = 'https:\/\/www.googleapis.com\/books\/v1\/' +\n                    'volumes?q=america&amp;projection=full';\n        List&lt;Map&lt;String,Object&gt;&gt; response =\n            DataSource.QueryUtils.filter(contexts, getData(url));\n        List&lt;Map&lt;String, Object&gt;&gt; countResponse =\n            new List&lt;Map&lt;String, Object&gt;&gt;();\n        Map&lt;String, Object&gt; countRow =\n            new Map&lt;String, Object&gt;();\n        countRow.put(\n            contexts.tableSelection.columnsSelected.get(0).columnName,\n            response.size());\n        countResponse.add(countRow);\n        return DataSource.TableResult.get(contexts, countResponse);\n    }\n\n    \/**\n     *   Called to do a full text search and get results from\n     *   the external system for SOSL queries and Salesforce\n     *   global searches.\n     *\n     *   The SearchContext argument represents the query to run\n     *   against a table in the external system.\n     *\n     *   Returns results for each table that the SearchContext\n     *   requested to be searched.\n     **\/\n    override global List&lt;DataSource.TableResult&gt; search(\n        DataSource.SearchContext contexts) {\n        List&lt;DataSource.TableResult&gt; results =\n            new List&lt;DataSource.TableResult&gt;();\n\n        for (Integer i =0; i&lt; contexts.tableSelections.size();i++) {\n            String entity = contexts.tableSelections&#91;i].tableSelected;\n            String url = 'https:\/\/www.googleapis.com\/books\/v1' +\n                        '\/volumes?q=' + contexts.searchPhrase;\n            results.add(DataSource.TableResult.get(true, null,\n                                                entity,\n                                                getData(url)));\n        }\n\n        return results;\n    }\n\n    \/**\n     *   Helper method to parse the data.\n     *   Returns a list of rows from the external system.\n     **\/\n    public List&lt;Map&lt;String, Object&gt;&gt; getData(String url) {\n        HttpResponse response = getResponse(url);\n        String body = response.getBody();\n\n        List&lt;Map&lt;String, Object&gt;&gt; rows =\n            new List&lt;Map&lt;String, Object&gt;&gt;();\n\n        Map&lt;String, Object&gt; responseBodyMap =\n            (Map&lt;String, Object&gt;)JSON.deserializeUntyped(body);\n\n    \/**\n     *   Checks errors.\n     **\/        \n        Map&lt;String, Object&gt; error =\n            (Map&lt;String, Object&gt;)responseBodyMap.get('error');\n        if (error!=null) {\n            List&lt;Object&gt; errorsList =\n                (List&lt;Object&gt;)error.get('errors');\n            Map&lt;String, Object&gt; errors =\n                (Map&lt;String, Object&gt;)errorsList&#91;0];\n            String messages = (String)errors.get('message');\n            throw new DataSource.OAuthTokenExpiredException(messages);\n        }\n\n        List&lt;Object&gt; sItems = (List&lt;Object&gt;)responseBodyMap.get('items');\n        if (sItems != null) {\n            for (Integer i=0; i&lt; sItems.size(); i++) {\n                Map&lt;String, Object&gt; item =\n                    (Map&lt;String, Object&gt;)sItems&#91;i];\n                rows.add(createRow(item));\n            }\n        } else {\n            rows.add(createRow(responseBodyMap));\n        }\n \n        return rows;\n    }\n\n    \/**\n     *   Helper method to populate a row based on source data.\n     *\n     *   The item argument maps to the data that\n     *   represents a row.\n     *\n     *   Returns an updated map with the External ID and\n     *   Display URL values.\n     **\/\n    public Map&lt;String, Object&gt; createRow(\n        Map&lt;String, Object&gt; item) {\n        Map&lt;String, Object&gt; row = new Map&lt;String, Object&gt;();\n        for ( String key : item.keySet() ){\n            if (key == 'id') {\n                row.put('ExternalId', item.get(key));\n            } else if (key == 'volumeInfo') {\n                Map&lt;String, Object&gt; volumeInfoMap =\n                    (Map&lt;String, Object&gt;)item.get(key);\n                row.put('title', volumeInfoMap.get('title'));\n                row.put('description',\n                        volumeInfoMap.get('description'));\n                row.put('DisplayUrl',\n                        volumeInfoMap.get('infoLink'));\n                row.put('publishedDate',\n                        volumeInfoMap.get('publishedDate'));\n                row.put('publisher',\n                        volumeInfoMap.get('publisher'));\n            }\n        }\n        return row;\n    }\n\n    \/**\n     *   Helper method to make the HTTP GET call.\n     *   The url argument is the URL of the external system.\n     *   Returns the response from the external system.\n     **\/\n    public HttpResponse getResponse(String url) {\n        Http httpProtocol = new Http();\n        HttpRequest request = new HttpRequest();\n        request.setEndPoint(url);\n        request.setMethod('GET');\n        request.setHeader('Authorization', 'Bearer '+\n                        this.connectionInfo.oauthToken);\n        HttpResponse response = httpProtocol.send(request);\n        return response;\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">BooksDataSourceProvider Class<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Extends the DataSource.Provider base class to create a\n *   custom adapter for Salesforce Connect. The class informs\n *   Salesforce of the functional and authentication\n *   capabilities that are supported by or required to connect\n *   to an external system.\n **\/\nglobal class BooksDataSourceProvider extends\n    DataSource.Provider {\n    \/**\n     *   Declares the types of authentication that can be used\n     *   to access the external system.\n     **\/   \n    override global List&lt;DataSource.AuthenticationCapability&gt;\n        getAuthenticationCapabilities() {\n        List&lt;DataSource.AuthenticationCapability&gt; capabilities =\n            new List&lt;DataSource.AuthenticationCapability&gt;();\n        capabilities.add(\n            DataSource.AuthenticationCapability.OAUTH);\n        capabilities.add(\n            DataSource.AuthenticationCapability.ANONYMOUS);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the functional capabilities that the\n     *   external system supports.\n     **\/\n    override global List&lt;DataSource.Capability&gt;\n        getCapabilities() {\n        List&lt;DataSource.Capability&gt; capabilities = new\n            List&lt;DataSource.Capability&gt;();\n        capabilities.add(DataSource.Capability.ROW_QUERY);\n        capabilities.add(DataSource.Capability.SEARCH);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the associated DataSource.Connection class.\n     **\/\n    override global DataSource.Connection getConnection(\n        DataSource.ConnectionParams connectionParams) {\n        return new BooksDataSourceConnection(connectionParams);\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u9002\u7528\u4e8e Salesforce Connect \u7684\u73af\u56de\u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/h2>\n\n\n\n<p>\u6b64\u793a\u4f8b\u8bf4\u660e\u5982\u4f55\u5904\u7406\u67e5\u8be2\u4e2d\u7684\u7b5b\u9009\u3002\u4e3a\u7b80\u5355\u8d77\u89c1\uff0c\u8fd9\u91cc \u793a\u4f8b\u5c06 Salesforce \u7ec4\u7ec7\u4f5c\u4e3a\u5916\u90e8\u7cfb\u7edf\u8fde\u63a5\u5230\u81ea\u8eab\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">LoopbackDataSourceConnection \u7c7b<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Extends the DataSource.Connection class to enable \n *   Salesforce to sync the external system\u00e2\u20ac\u2122s schema \n *   and to handle queries and searches of the external data. \n **\/\nglobal class LoopbackDataSourceConnection \n    extends DataSource.Connection {\n\n    \/**\n     *   Constructors.\n     **\/\n    global LoopbackDataSourceConnection(\n        DataSource.ConnectionParams connectionParams) {\n    }\n    global LoopbackDataSourceConnection() {}\n    \n    \/**\n     *   Called when an external object needs to get a list of \n     *   schema from the external data source, for example when \n     *   the administrator clicks \u00e2\u20ac\u0153Validate and Sync\u00e2\u20ac\u009d in the \n     *   user interface for the external data source.   \n     **\/\n    override global List&lt;DataSource.Table&gt; sync() {\n        List&lt;DataSource.Table&gt; tables = \n            new List&lt;DataSource.Table&gt;();        \n        List&lt;DataSource.Column&gt; columns;\n        columns = new List&lt;DataSource.Column&gt;();\n        columns.add(DataSource.Column.text('ExternalId', 255));\n        columns.add(DataSource.Column.url('DisplayUrl'));\n        columns.add(DataSource.Column.text('Name', 255));\n        columns.add(\n            DataSource.Column.number('NumberOfEmployees', 18, 0));\n        tables.add(\n            DataSource.Table.get('Looper', 'Name', columns));\n        return tables;\n    }\n    \n    \/**\n     *   Called to query and get results from the external \n     *   system for SOQL queries, list views, and detail pages \n     *   for an external object that\u00e2\u20ac\u2122s associated with the \n     *   external data source.\n     *   \n     *   The QueryContext argument represents the query to run \n     *   against a table in the external system.\n     *   \n     *   Returns a list of rows as the query results.\n     **\/\n    override global DataSource.TableResult \n        query(DataSource.QueryContext context) {\n        if (context.tableSelection.columnsSelected.size() == 1 &amp;&amp;\n            context.tableSelection.columnsSelected.get(0).aggregation ==\n                DataSource.QueryAggregation.COUNT) {\n            integer count = execCount(getCountQuery(context));\n            List&lt;Map&lt;String, Object&gt;&gt; countResponse =\n                new List&lt;Map&lt;String, Object&gt;&gt;();\n            Map&lt;String, Object&gt; countRow =\n                new Map&lt;String, Object&gt;();\n            countRow.put(\n                context.tableSelection.columnsSelected.get(0).columnName,\n                count);\n            countResponse.add(countRow);\n            return DataSource.TableResult.get(context,countResponse);\n        } else {\n            List&lt;Map&lt;String,Object&gt;&gt; rows = execQuery(\n                getSoqlQuery(context));\n            return DataSource.TableResult.get(context,rows);\n        }\n    }\n    \n    \/**\n     *   Called to do a full text search and get results from\n     *   the external system for SOSL queries and Salesforce\n     *   global searches.\n     *   \n     *   The SearchContext argument represents the query to run \n     *   against a table in the external system.\n     *   \n     *   Returns results for each table that the SearchContext \n     *   requested to be searched.\n     **\/\n    override global List&lt;DataSource.TableResult&gt; \n        search(DataSource.SearchContext context) {        \n        return DataSource.SearchUtils.searchByName(context, this);\n    }\n\n    \/**\n     *   Helper method to execute the SOQL query and \n     *   return the results.\n     **\/\n    private List&lt;Map&lt;String,Object&gt;&gt; \n        execQuery(String soqlQuery) {\n        List&lt;Account&gt; objs = Database.query(soqlQuery);\n        List&lt;Map&lt;String,Object&gt;&gt; rows = \n            new List&lt;Map&lt;String,Object&gt;&gt;();\n        for (Account obj : objs) {\n            Map&lt;String,Object&gt; row = new Map&lt;String,Object&gt;();\n            row.put('Name', obj.Name);\n            row.put('NumberOfEmployees', obj.NumberOfEmployees);\n            row.put('ExternalId', obj.Id);\n            row.put('DisplayUrl', \n                URL.getOrgDomainUrl().toExternalForm() + \n                    obj.Id);\n            rows.add(row);\n        }\n        return rows;\n    }\n\n    \/**\n     *   Helper method to get aggregate count.\n     **\/\n    private integer execCount(String soqlQuery) {\n        integer count = Database.countQuery(soqlQuery);\n        return count;\n    }\n\n    \/**\n     *   Helper method to create default aggregate query.\n     **\/\n    private String getCountQuery(DataSource.QueryContext context) {\n        String baseQuery = 'SELECT COUNT() FROM Account';\n        String filter = getSoqlFilter('', \n            context.tableSelection.filter);\n        if (filter.length() &gt; 0)\n            return baseQuery + ' WHERE ' + filter;\n        return baseQuery;\n    }\n\n    \/**\n     *   Helper method to create default query.\n     **\/\n    private String getSoqlQuery(DataSource.QueryContext context) {\n        String baseQuery = \n            'SELECT Id,Name,NumberOfEmployees FROM Account';\n        String filter = getSoqlFilter('', \n            context.tableSelection.filter);\n        if (filter.length() &gt; 0)\n            return baseQuery + ' WHERE ' + filter;\n        return baseQuery;\n    }\n\n    \/**\n     *   Helper method to handle query filter.\n     **\/\n    private String getSoqlFilter(String query, \n        DataSource.Filter filter) {\n        if (filter == null) {\n            return query;\n        }\n        String append;\n        DataSource.FilterType type = filter.type;\n        List&lt;Map&lt;String,Object&gt;&gt; retainedRows = \n            new List&lt;Map&lt;String,Object&gt;&gt;();\n        if (type == DataSource.FilterType.NOT_) {\n            DataSource.Filter subfilter = filter.subfilters.get(0);\n            append = getSoqlFilter('NOT', subfilter);\n        } else if (type == DataSource.FilterType.AND_) {\n            append =  \n                getSoqlFilterCompound('AND', filter.subfilters);\n        } else if (type == DataSource.FilterType.OR_) {\n            append = \n                getSoqlFilterCompound('OR', filter.subfilters);\n        } else {\n            append = getSoqlFilterExpression(filter);\n        }\n        return query + ' ' + append;\n    }\n    \n    \/**\n     *   Helper method to handle query subfilters.\n     **\/\n    private String getSoqlFilterCompound(String operator, \n        List&lt;DataSource.Filter&gt; subfilters) {\n        String expression = ' (';\n        boolean first = true;\n        for (DataSource.Filter subfilter : subfilters) {\n            if (first)\n                first = false;\n            else\n                expression += ' ' + operator + ' ';\n            expression += getSoqlFilter('', subfilter);\n        }\n        expression += ') ';\n        return expression;\n    }\n    \n    \/**\n     *   Helper method to handle query filter expressions.\n     **\/\n    private String getSoqlFilterExpression(\n        DataSource.Filter filter) {\n        String columnName = filter.columnName;\n        String operator;\n        Object expectedValue = filter.columnValue;\n        if (filter.type == DataSource.FilterType.EQUALS) {\n            operator = '=';\n        } else if (filter.type == \n            DataSource.FilterType.NOT_EQUALS) {\n            operator = '&lt;&gt;';\n        } else if (filter.type == \n            DataSource.FilterType.LESS_THAN) {\n            operator = '&lt;';\n        } else if (filter.type == \n            DataSource.FilterType.GREATER_THAN) {\n            operator = '&gt;';\n        } else if (filter.type == \n            DataSource.FilterType.LESS_THAN_OR_EQUAL_TO) {\n            operator = '&lt;=';\n        } else if (filter.type == \n            DataSource.FilterType.GREATER_THAN_OR_EQUAL_TO) {\n            operator = '&gt;=';\n        } else if (filter.type == \n            DataSource.FilterType.STARTS_WITH) {\n            return mapColumnName(columnName) + \n            ' LIKE \\'' + String.valueOf(expectedValue) + '%\\'';\n        } else if (filter.type == \n            DataSource.FilterType.ENDS_WITH) {\n            return mapColumnName(columnName) + \n            ' LIKE \\'%' + String.valueOf(expectedValue) + '\\'';\n        } else if (filter.type == \n            DataSource.FilterType.LIKE_) {\n            return mapColumnName(columnName) + \n            ' LIKE \\'' + String.valueOf(expectedValue) + '\\'';\n        } else {\n            throwException(\n            'Implementing other filter types is left as an exercise for the reader: ' \n            + filter.type);\n        }\n        return mapColumnName(columnName) + \n            ' ' + operator + ' ' + wrapValue(expectedValue);\n    }\n    \n    \/**\n     *   Helper method to map column names.\n     **\/\n    private String mapColumnName(String apexName) {\n        if (apexName.equalsIgnoreCase('ExternalId'))\n            return 'Id';\n        if (apexName.equalsIgnoreCase('DisplayUrl'))\n            return 'Id';\n        return apexName;\n    }\n\n    \/**\n    *   Helper method to wrap expression Strings with quotes.\n    **\/\n    private String wrapValue(Object foundValue) {\n        if (foundValue instanceof String)\n            return '\\'' + String.valueOf(foundValue) + '\\'';\n        return String.valueOf(foundValue);\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">LoopbackDataSourceProvider Class<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Extends the DataSource.Provider base class to create a \n *   custom adapter for Salesforce Connect. The class informs \n *   Salesforce of the functional and authentication \n *   capabilities that are supported by or required to connect \n *   to an external system.\n **\/\nglobal class LoopbackDataSourceProvider \n    extends DataSource.Provider {\n    \n    \/**\n     *   Declares the types of authentication that can be used \n     *   to access the external system.\n     **\/\n    override global List&lt;DataSource.AuthenticationCapability&gt; \n        getAuthenticationCapabilities() {\n        List&lt;DataSource.AuthenticationCapability&gt; capabilities = \n            new List&lt;DataSource.AuthenticationCapability&gt;();\n        capabilities.add(\n            DataSource.AuthenticationCapability.ANONYMOUS);\n        capabilities.add(\n            DataSource.AuthenticationCapability.BASIC);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the functional capabilities that the \n     *   external system supports.\n     **\/\n    override global List&lt;DataSource.Capability&gt; \n        getCapabilities() {\n        List&lt;DataSource.Capability&gt; capabilities = \n            new List&lt;DataSource.Capability&gt;();\n        capabilities.add(DataSource.Capability.ROW_QUERY);\n        capabilities.add(DataSource.Capability.SEARCH);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the associated DataSource.Connection class.\n     **\/\n    override global DataSource.Connection \n        getConnection(DataSource.ConnectionParams connectionParams) {\n        return new LoopbackDataSourceConnection();\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u9002\u7528\u4e8e Salesforce Connect \u7684 GitHub \u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/h2>\n\n\n\n<p>\u6b64\u793a\u4f8b\u6f14\u793a\u5982\u4f55\u652f\u6301\u95f4\u63a5\u67e5\u627e\u5173\u7cfb\u3002\u95f4\u63a5 \u67e5\u627e\u5173\u7cfb\u5c06\u5b50\u5916\u90e8\u5bf9\u8c61\u94fe\u63a5\u5230\u7236\u6807\u51c6\u5bf9\u8c61\u6216\u81ea\u5b9a\u4e49\u5bf9\u8c61 \u5bf9\u8c61\u3002<\/p>\n\n\n\n<p>\u82e5\u8981\u4f7f\u6b64\u793a\u4f8b\u6b63\u5e38\u5de5\u4f5c\uff0c\u8bf7\u5728\u201c\u8054\u7cfb\u4eba\u201d\u6807\u51c6\u5bf9\u8c61\u4e0a\u521b\u5efa\u4e00\u4e2a\u81ea\u5b9a\u4e49\u5b57\u6bb5\u3002\u547d\u540d\u81ea\u5b9a\u4e49\u9879 \u5b57\u6bb5\uff0c\u4f7f\u5176\u6210\u4e3a\u957f\u5ea6\u4e3a 39 \u7684\u6587\u672c\u5b57\u6bb5\uff0c\u5e76\u4e14 \u9009\u62e9 \u548c \u5c5e\u6027\u3002\u53e6\u5916\uff0c\u5c06 https:\/\/api.github.com \u6dfb\u52a0\u5230 \u60a8\u7684\u8fdc\u7a0b\u7ad9\u70b9\u8bbe\u7f6e\u3002<kbd>github_username<\/kbd><samp>External ID<\/samp><samp>Unique<\/samp><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">GitHubDataSourceConnection \u7c7b<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Defines the connection to GitHub REST API v3 to support\n *   querying of GitHub profiles.\n *   Extends the DataSource.Connection class to enable\n *   Salesforce to sync the external system\u2019s schema\n *   and to handle queries and searches of the external data.\n **\/\nglobal class GitHubDataSourceConnection extends\n        DataSource.Connection {\n    private DataSource.ConnectionParams connectionInfo;\n\n    \/**\n     *   Constructor for GitHubDataSourceConnection\n     **\/\n    global GitHubDataSourceConnection(\n            DataSource.ConnectionParams connectionInfo) {\n        this.connectionInfo = connectionInfo;\n    }\n\n    \/**\n     *   Called to query and get results from the external \n     *   system for SOQL queries, list views, and detail pages \n     *   for an external object that\u2019s associated with the \n     *   external data source.\n     *   \n     *   The queryContext argument represents the query to run \n     *   against a table in the external system.\n     *   \n     *   Returns a list of rows as the query results.\n     **\/\n    override global DataSource.TableResult query(\n            DataSource.QueryContext context) {\n        DataSource.Filter filter = context.tableSelection.filter;\n        String url;\n        if (filter != null) {\n            String thisColumnName = filter.columnName;\n            if (thisColumnName != null &amp;&amp;\n               (thisColumnName.equals('ExternalId') ||\n                thisColumnName.equals('login')))\n                url = 'https:\/\/api.github.com\/users\/'\n                        + filter.columnValue;\n            else\n                    url = 'https:\/\/api.github.com\/users';\n        } else {\n            url = 'https:\/\/api.github.com\/users';\n        }\n\n        \/**\n         * Filters, sorts, and applies limit and offset clauses.\n         **\/\n        List&lt;Map&lt;String, Object&gt;&gt; rows =\n                DataSource.QueryUtils.process(context, getData(url));\n        return DataSource.TableResult.get(true, null,\n                context.tableSelection.tableSelected, rows);\n    }\n\n    \/**\n     *   Defines the schema for the external system. \n     *   Called when the administrator clicks \u201cValidate and Sync\u201d\n     *   in the user interface for the external data source.\n     **\/\n    override global List&lt;DataSource.Table&gt; sync() {\n        List&lt;DataSource.Table&gt; tables =\n                new List&lt;DataSource.Table&gt;();\n        List&lt;DataSource.Column&gt; columns;\n        columns = new List&lt;DataSource.Column&gt;();\n\n        \/\/ Defines the indirect lookup field. (For this to work,\n        \/\/ make sure your Contact standard object has a\n        \/\/ custom unique, external ID field called github_username.)\n        columns.add(DataSource.Column.indirectLookup(\n                'login', 'Contact', 'github_username__c'));\n\n        columns.add(DataSource.Column.text('id', 255));\n        columns.add(DataSource.Column.text('name',255));\n        columns.add(DataSource.Column.text('company',255));\n        columns.add(DataSource.Column.text('bio',255));\n        columns.add(DataSource.Column.text('followers',255));\n        columns.add(DataSource.Column.text('following',255));\n        columns.add(DataSource.Column.url('html_url'));\n        columns.add(DataSource.Column.url('DisplayUrl'));\n        columns.add(DataSource.Column.text('ExternalId',255));\n        tables.add(DataSource.Table.get('githubProfile','login',\n                columns));\n        return tables;\n    }\n\n    \/**\n     *   Called to do a full text search and get results from\n     *   the external system for SOSL queries and Salesforce\n     *   global searches.\n     *\n     *   The SearchContext argument represents the query to run\n     *   against a table in the external system.\n     *\n     *   Returns results for each table that the SearchContext\n     *   requested to be searched.\n     **\/\n    override global List&lt;DataSource.TableResult&gt; search(\n            DataSource.SearchContext context) {\n        List&lt;DataSource.TableResult&gt; results =\n                new List&lt;DataSource.TableResult&gt;();\n\n        for (Integer i =0;i&lt; context.tableSelections.size();i++) {\n            String entity = context.tableSelections&#91;i].tableSelected;\n\n            \/\/ Search usernames\n            String url = 'https:\/\/api.github.com\/users\/'\n                            + context.searchPhrase;\n            results.add(DataSource.TableResult.get(\n                    true, null, entity, getData(url)));\n        }\n\n        return results;\n    }\n\n    \/**\n     *   Helper method to parse the data.\n     *   The url argument is the URL of the external system.\n     *   Returns a list of rows from the external system.\n     **\/\n    public List&lt;Map&lt;String, Object&gt;&gt; getData(String url) {\n        String response = getResponse(url);\n\n        \/\/ Standardize response string\n        if (!response.contains('\"items\":')) {\n            if (response.substring(0,1).equals('{')) {\n                response = '&#91;' + response  + ']';\n            }\n            response = '{\"items\": ' + response + '}';\n        }\n\n        List&lt;Map&lt;String, Object&gt;&gt; rows =\n                new List&lt;Map&lt;String, Object&gt;&gt;();\n\n        Map&lt;String, Object&gt; responseBodyMap = (Map&lt;String, Object&gt;)\n                JSON.deserializeUntyped(response);\n\n        \/**\n         *   Checks errors.\n         **\/\n        Map&lt;String, Object&gt; error =\n                (Map&lt;String, Object&gt;)responseBodyMap.get('error');\n        if (error!=null) {\n            List&lt;Object&gt; errorsList =\n                    (List&lt;Object&gt;)error.get('errors');\n            Map&lt;String, Object&gt; errors =\n                    (Map&lt;String, Object&gt;)errorsList&#91;0];\n            String errorMessage = (String)errors.get('message');\n            throw new \n                    DataSource.OAuthTokenExpiredException(errorMessage);\n        }\n\n        List&lt;Object&gt; fileItems = \n            (List&lt;Object&gt;)responseBodyMap.get('items');\n        if (fileItems != null) {\n            for (Integer i=0; i &lt; fileItems.size(); i++) {\n                Map&lt;String, Object&gt; item =\n                        (Map&lt;String, Object&gt;)fileItems&#91;i];\n                rows.add(createRow(item));\n            }\n        } else {\n            rows.add(createRow(responseBodyMap));\n        }\n\n        return rows;\n    }\n\n    \/**\n     *   Helper method to populate the External ID and Display\n     *   URL fields on external object records based on the 'id'\n     *   value that\u2019s sent by the external system.\n     *\n     *   The Map&lt;String, Object&gt; item parameter maps to the data\n     *   that represents a row.\n     *\n     *   Returns an updated map with the External ID and\n     *   Display URL values.\n     **\/\n    public Map&lt;String, Object&gt; createRow(\n            Map&lt;String, Object&gt; item){\n        Map&lt;String, Object&gt; row = new Map&lt;String, Object&gt;();\n        for ( String key : item.keySet() ) {\n            if (key == 'login') {\n                row.put('ExternalId', item.get(key));\n            } else if (key=='html_url') {\n                row.put('DisplayUrl', item.get(key));\n            }\n\n            row.put(key, item.get(key));\n        }\n        return row;\n    }\n\n    \/**\n     *   Helper method to make the HTTP GET call.\n     *   The url argument is the URL of the external system.\n     *   Returns the response from the external system.\n     **\/\n    public String getResponse(String url) {\n        \/\/ Perform callouts for production (non-test) results.\n        Http httpProtocol = new Http();\n        HttpRequest request = new HttpRequest();\n        request.setEndPoint(url);\n        request.setMethod('GET');\n        HttpResponse response = httpProtocol.send(request);\n        return response.getBody();\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">GitHubDataSourceProvider \u7c7b<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Extends the DataSource.Provider base class to create a\n *   custom adapter for Salesforce Connect. The class informs\n *   Salesforce of the functional and authentication\n *   capabilities that are supported by or required to connect\n *   to an external system.\n **\/\nglobal class GitHubDataSourceProvider\n        extends DataSource.Provider {\n\n    \/**\n     *   For simplicity, this example declares that the external \n     *   system doesn\u2019t require authentication by returning\n     *   AuthenticationCapability.ANONYMOUS as the sole entry \n     *   in the list of authentication capabilities.\n     **\/\n    override global List&lt;DataSource.AuthenticationCapability&gt;\n    getAuthenticationCapabilities() {\n        List&lt;DataSource.AuthenticationCapability&gt; capabilities =\n                new List&lt;DataSource.AuthenticationCapability&gt;();\n        capabilities.add(\n                DataSource.AuthenticationCapability.ANONYMOUS);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the functional capabilities that the\n     *   external system supports, in this case\n     *   only SOQL queries.\n     **\/\n    override global List&lt;DataSource.Capability&gt;\n    getCapabilities() {\n        List&lt;DataSource.Capability&gt; capabilities =\n                new List&lt;DataSource.Capability&gt;();\n        capabilities.add(DataSource.Capability.ROW_QUERY);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the associated DataSource.Connection class.\n     **\/\n    override global DataSource.Connection getConnection(\n            DataSource.ConnectionParams connectionParams) {\n        return new GitHubDataSourceConnection(connectionParams);\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u9002\u7528\u4e8e Salesforce Connect \u7684 Stack Overflow \u81ea\u5b9a\u4e49\u9002\u914d\u5668<\/h2>\n\n\n\n<p>\u6b64\u793a\u4f8b\u8bf4\u660e\u5982\u4f55\u652f\u6301\u5916\u90e8\u67e5\u627e\u5173\u7cfb\u548c\u591a\u4e2a \u8868\u3002\u5916\u90e8\u67e5\u627e\u5173\u7cfb\u94fe\u63a5\u5b50\u6807\u51c6\u5bf9\u8c61\u3001\u81ea\u5b9a\u4e49\u5bf9\u8c61\u6216\u5916\u90e8\u5bf9\u8c61 \u6dfb\u52a0\u5230\u7236\u5916\u90e8\u5bf9\u8c61\u3002\u6bcf\u4e2a\u8868\u90fd\u53ef\u4ee5\u6210\u4e3a Salesforce \u4e2d\u7684\u5916\u90e8\u5bf9\u8c61 \u7ec4\u7ec7\u3002<\/p>\n\n\n\n<p>\u82e5\u8981\u4f7f\u6b64\u793a\u4f8b\u6b63\u5e38\u5de5\u4f5c\uff0c\u8bf7\u5728\u201c\u8054\u7cfb\u4eba\u201d\u6807\u51c6\u5bf9\u8c61\u4e0a\u521b\u5efa\u4e00\u4e2a\u81ea\u5b9a\u4e49\u5b57\u6bb5\u3002\u5c06 \u81ea\u5b9a\u4e49\u5b57\u6bb5\u201cgithub_username\u201d\uff0c\u7136\u540e\u9009\u62e9 \u548c \u5c5e\u6027\u3002<samp>External ID<\/samp><samp>Unique<\/samp><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">StackOverflowDataSourceConnection \u7c7b<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Defines the connection to Stack Exchange API v2.2 to support\n *   querying of Stack Overflow users (stackoverflowUser)\n *   and posts (stackoverflowPost).\n *   Extends the DataSource.Connection class to enable\n *   Salesforce to sync the external system\u2019s schema\n *   and to handle queries of the external data.\n **\/\nglobal class StackOverflowDataSourceConnection extends\n        DataSource.Connection {\n    private DataSource.ConnectionParams connectionInfo;\n\n    \/**\n     *   Constructor for StackOverflowDataSourceConnection\n     **\/\n    global StackOverflowDataSourceConnection(\n            DataSource.ConnectionParams connectionInfo) {\n        this.connectionInfo = connectionInfo;\n    }\n\n    \/**\n     *   Defines the schema for the external system. \n     *   Called when the administrator clicks \u201cValidate and Sync\u201d\n     *   in the user interface for the external data source.\n     **\/\n    override global List&lt;DataSource.Table&gt; sync() {\n        List&lt;DataSource.Table&gt; tables =\n                new List&lt;DataSource.Table&gt;();\n\n        \/\/ Defines columns for the table of Stack OverFlow posts\n        List&lt;DataSource.Column&gt; postColumns =\n          new List&lt;DataSource.Column&gt;();\n\n        \/\/ Defines the external lookup field.\n        postColumns.add(DataSource.Column.externalLookup(\n          'owner_id', 'stackoverflowUser__x'));\n        postColumns.add(DataSource.Column.text('title', 255));\n        postColumns.add(DataSource.Column.text('view_count', 255));\n        postColumns.add(DataSource.Column.text('question_id',255));\n        postColumns.add(DataSource.Column.text('creation_date',255));\n        postColumns.add(DataSource.Column.text('score',255));\n        postColumns.add(DataSource.Column.url('link'));\n        postColumns.add(DataSource.Column.url('DisplayUrl'));\n        postColumns.add(DataSource.Column.text('ExternalId',255));\n\n        tables.add(DataSource.Table.get('stackoverflowPost','title',\n          postColumns));\n\n        \/\/ Defines columns for the table of Stack OverFlow users\n        List&lt;DataSource.Column&gt; userColumns =\n          new List&lt;DataSource.Column&gt;();\n        userColumns.add(DataSource.Column.text('user_id', 255));\n        userColumns.add(DataSource.Column.text('display_name', 255));\n        userColumns.add(DataSource.Column.text('location',255));\n        userColumns.add(DataSource.Column.text('creation_date',255));\n        userColumns.add(DataSource.Column.url('website_url',255));\n        userColumns.add(DataSource.Column.text('reputation',255));\n        userColumns.add(DataSource.Column.url('link'));\n        userColumns.add(DataSource.Column.url('DisplayUrl'));\n        userColumns.add(DataSource.Column.text('ExternalId',255));\n\n        tables.add(DataSource.Table.get('stackoverflowUser',\n                'Display_name', userColumns));\n\n        return tables;\n    }\n\n    \/**\n     *   Called to query and get results from the external\n     *   system for SOQL queries, list views, and detail pages\n     *   for an external object that\u2019s associated with the\n     *   external data source.\n     *\n     *   The QueryContext argument represents the query to run\n     *   against a table in the external system.\n     *\n     *   Returns a list of rows as the query results.\n     **\/\n    override global DataSource.TableResult query(\n            DataSource.QueryContext context) {\n        DataSource.Filter filter = context.tableSelection.filter;\n        String url;\n\n        \/\/ Sets the URL to query Stack Overflow posts\n        if (context.tableSelection.tableSelected\n.equals('stackoverflowPost')) {\n            if (filter != null) {\n                String thisColumnName = filter.columnName;\n                if (thisColumnName != null &amp;&amp;\n                        thisColumnName.equals('ExternalId'))\n                    url = 'https:\/\/api.stackexchange.com\/2.2\/'\n                            + 'questions\/' + filter.columnValue\n                            + '?order=desc&amp;sort=activity'\n                            + '&amp;site=stackoverflow';\n                else\n                        url = 'https:\/\/api.stackexchange.com\/2.2\/'\n                                + 'questions'\n                                + '?order=desc&amp;sort=activity'\n                                + '&amp;site=stackoverflow';\n            } else {\n                url = 'https:\/\/api.stackexchange.com\/2.2\/'\n                        + 'questions'\n                        + '?order=desc&amp;sort=activity'\n                        + '&amp;site=stackoverflow';\n            }\n        \/\/ Sets the URL to query Stack Overflow users\n        } else if (context.tableSelection.tableSelected\n.equals('stackoverflowUser')) {\n            if (filter != null) {\n                String thisColumnName = filter.columnName;\n                if (thisColumnName != null &amp;&amp;\n                        thisColumnName.equals('ExternalId'))\n                    url = 'https:\/\/api.stackexchange.com\/2.2\/'\n                            + 'users\/' + filter.columnValue\n                            + '?order=desc&amp;sort=reputation'\n                            + '&amp;site=stackoverflow';\n                else\n                    url = 'https:\/\/api.stackexchange.com\/2.2\/'\n                            + 'users' + \n'?order=desc&amp;sort=reputation&amp;site=stackoverflow';\n            } else {\n                url = 'https:\/\/api.stackexchange.com\/2.2\/'\n                        + 'users' + '?order=desc&amp;sort=reputation'\n                        + '&amp;site=stackoverflow';\n            }\n        }\n\n        \/**\n         * Filters, sorts, and applies limit and offset clauses.\n         **\/\n        List&lt;Map&lt;String, Object&gt;&gt; rows =\n                DataSource.QueryUtils.process(context, getData(url));\n        return DataSource.TableResult.get(true, null,\n                context.tableSelection.tableSelected, rows);\n    }\n\n    \/**\n     *   Helper method to parse the data.\n     *   The url argument is the URL of the external system.\n     *   Returns a list of rows from the external system.\n     **\/\n    public List&lt;Map&lt;String, Object&gt;&gt; getData(String url) {\n        String response = getResponse(url);\n\n        List&lt;Map&lt;String, Object&gt;&gt; rows =\n                new List&lt;Map&lt;String, Object&gt;&gt;();\n\n        Map&lt;String, Object&gt; responseBodyMap = (Map&lt;String, Object&gt;)\n                JSON.deserializeUntyped(response);\n\n        \/**\n         *   Checks errors.\n         **\/\n        Map&lt;String, Object&gt; error =\n                (Map&lt;String, Object&gt;)responseBodyMap.get('error');\n        if (error!=null) {\n            List&lt;Object&gt; errorsList =\n                    (List&lt;Object&gt;)error.get('errors');\n            Map&lt;String, Object&gt; errors =\n                    (Map&lt;String, Object&gt;)errorsList&#91;0];\n            String errorMessage = (String)errors.get('message');\n            throw new \n                    DataSource.OAuthTokenExpiredException(errorMessage);\n        }\n\n        List&lt;Object&gt; fileItems=\n            (List&lt;Object&gt;)responseBodyMap.get('items');\n        if (fileItems != null) {\n            for (Integer i=0; i &lt; fileItems.size(); i++) {\n                Map&lt;String, Object&gt; item =\n                        (Map&lt;String, Object&gt;)fileItems&#91;i];\n                rows.add(createRow(item));\n            }\n        } else {\n            rows.add(createRow(responseBodyMap));\n        }\n\n        return rows;\n    }\n\n    \/**\n     *   Helper method to populate the External ID and Display\n     *   URL fields on external object records based on the 'id'\n     *   value that\u2019s sent by the external system.\n     *\n     *   The Map&lt;String, Object&gt; item parameter maps to the data\n     *   that represents a row.\n     *\n     *   Returns an updated map with the External ID and\n     *   Display URL values.\n     **\/\n    public Map&lt;String, Object&gt; createRow(\n            Map&lt;String, Object&gt; item) {\n        Map&lt;String, Object&gt; row = new Map&lt;String, Object&gt;();\n        for ( String key : item.keySet() ) {\n            if (key.equals('question_id') || key.equals('user_id')) {\n                row.put('ExternalId', item.get(key));\n            } else if (key.equals('link')) {\n                row.put('DisplayUrl', item.get(key));\n            } else if (key.equals('owner')) {\n                Map&lt;String, Object&gt; ownerMap =\n                (Map&lt;String, Object&gt;)item.get(key);\n                row.put('owner_id', ownerMap.get('user_id'));\n            }\n\n            row.put(key, item.get(key));\n        }\n        return row;\n    }\n\n    \/**\n     *   Helper method to make the HTTP GET call.\n     *   The url argument is the URL of the external system.\n     *   Returns the response from the external system.\n     **\/\n    public String getResponse(String url) {\n        \/\/ Perform callouts for production (non-test) results.\n        Http httpProtocol = new Http();\n        HttpRequest request = new HttpRequest();\n        request.setEndPoint(url);\n        request.setMethod('GET');\n        HttpResponse response = httpProtocol.send(request);\n        return response.getBody();\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">StackOverflowPostDataSourceProvider Class<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n *   Extends the DataSource.Provider base class to create a\n *   custom adapter for Salesforce Connect. The class informs\n *   Salesforce of the functional and authentication\n *   capabilities that are supported by or required to connect\n *   to an external system.\n **\/\nglobal class StackOverflowPostDataSourceProvider\n        extends DataSource.Provider {\n\n    \/**\n     *   For simplicity, this example declares that the external \n     *   system doesn\u2019t require authentication by returning\n     *   AuthenticationCapability.ANONYMOUS as the sole entry \n     *   in the list of authentication capabilities.\n     **\/\n    override global List&lt;DataSource.AuthenticationCapability&gt;\n    getAuthenticationCapabilities() {\n        List&lt;DataSource.AuthenticationCapability&gt; capabilities =\n                new List&lt;DataSource.AuthenticationCapability&gt;();\n        capabilities.add(\n                DataSource.AuthenticationCapability.ANONYMOUS);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the functional capabilities that the\n     *   external system supports, in this case\n     *   only SOQL queries.\n     **\/\n    override global List&lt;DataSource.Capability&gt;\n    getCapabilities() {\n        List&lt;DataSource.Capability&gt; capabilities =\n                new List&lt;DataSource.Capability&gt;();\n        capabilities.add(DataSource.Capability.ROW_QUERY);\n        return capabilities;\n    }\n\n    \/**\n     *   Declares the associated DataSource.Connection class.\n     **\/\n    override global DataSource.Connection getConnection(\n            DataSource.ConnectionParams connectionParams) {\n        return new \n            StackOverflowDataSourceConnection(connectionParams);\n    }\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u3002\u4f7f\u7528 Apex Connector Framework\uff0c\u7528\u4e8e\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u6570\u636e\u5e76\u5728\u672c\u5730\u5408\u6210\u6570\u636e\u3002 Salesforce Connect \u5728 Salesforce \u5916\u90e8\u5bf9\u8c61\u4e2d\u8868\u793a\u8be5\u6570\u636e\uff0c\u4f7f\u7528\u6237\u548c Lightning Platform \u53ef\u4e0e\u5b58\u50a8\u5728 Salesforce \u5916\u90e8\u7684\u6570\u636e\u65e0\u7f1d\u4ea4\u4e92 \u7ec4\u7ec7\u3002<\/p>\n","protected":false},"author":1,"featured_media":3746,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[199],"tags":[220],"class_list":["post-3745","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-apex-","tag-salesforce-"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v21.7 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Salesforce \u8fde\u63a5 - \u767d\u767d\u968f\u7b14<\/title>\n<meta name=\"description\" content=\"Salesforce \u8fde\u63a5 Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u3002\u4f7f\u7528 Apex Connector Framework\uff0c\u7528\u4e8e\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u6570\u636e\u5e76\u5728\u672c\u5730\u5408\u6210\u6570\u636e\u3002 Salesforce Connect \u5728 Salesforce \u5916\u90e8\u5bf9\u8c61\u4e2d\u8868\u793a\u8be5\u6570\u636e\uff0c\u4f7f\u7528\u6237\u548c Lightning Platform \u53ef\u4e0e\u5b58\u50a8\u5728 Salesforce \u5916\u90e8\u7684\u6570\u636e\u65e0\u7f1d\u4ea4\u4e92 \u7ec4\u7ec7\u3002\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"http:\/\/www.ponybai.com\/?p=3745\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Salesforce \u8fde\u63a5 - \u767d\u767d\u968f\u7b14\" \/>\n<meta property=\"og:description\" content=\"Salesforce \u8fde\u63a5 Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u3002\u4f7f\u7528 Apex Connector Framework\uff0c\u7528\u4e8e\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u6570\u636e\u5e76\u5728\u672c\u5730\u5408\u6210\u6570\u636e\u3002 Salesforce Connect \u5728 Salesforce \u5916\u90e8\u5bf9\u8c61\u4e2d\u8868\u793a\u8be5\u6570\u636e\uff0c\u4f7f\u7528\u6237\u548c Lightning Platform \u53ef\u4e0e\u5b58\u50a8\u5728 Salesforce \u5916\u90e8\u7684\u6570\u636e\u65e0\u7f1d\u4ea4\u4e92 \u7ec4\u7ec7\u3002\" \/>\n<meta property=\"og:url\" content=\"http:\/\/www.ponybai.com\/?p=3745\" \/>\n<meta property=\"og:site_name\" content=\"\u767d\u767d\u968f\u7b14\" \/>\n<meta property=\"article:published_time\" content=\"2023-12-20T09:32:10+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-11-25T09:49:02+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/www.ponybai.com\/wp-content\/uploads\/2023\/11\/datasource.png\" \/>\n\t<meta property=\"og:image:width\" content=\"755\" \/>\n\t<meta property=\"og:image:height\" content=\"207\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"ponybai\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"ponybai\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"http:\/\/www.ponybai.com\/?p=3745#article\",\"isPartOf\":{\"@id\":\"http:\/\/www.ponybai.com\/?p=3745\"},\"author\":{\"name\":\"ponybai\",\"@id\":\"http:\/\/www.ponybai.com\/#\/schema\/person\/935c7592c850c65e1e5eba4530dbf883\"},\"headline\":\"Salesforce \u8fde\u63a5\",\"datePublished\":\"2023-12-20T09:32:10+00:00\",\"dateModified\":\"2023-11-25T09:49:02+00:00\",\"mainEntityOfPage\":{\"@id\":\"http:\/\/www.ponybai.com\/?p=3745\"},\"wordCount\":762,\"publisher\":{\"@id\":\"http:\/\/www.ponybai.com\/#\/schema\/person\/935c7592c850c65e1e5eba4530dbf883\"},\"keywords\":[\"Salesforce \u8fde\u63a5\"],\"articleSection\":[\"Apex \u5f00\u53d1\u4eba\u5458\u6307\u5357\"],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"WebPage\",\"@id\":\"http:\/\/www.ponybai.com\/?p=3745\",\"url\":\"http:\/\/www.ponybai.com\/?p=3745\",\"name\":\"Salesforce \u8fde\u63a5 - \u767d\u767d\u968f\u7b14\",\"isPartOf\":{\"@id\":\"http:\/\/www.ponybai.com\/#website\"},\"datePublished\":\"2023-12-20T09:32:10+00:00\",\"dateModified\":\"2023-11-25T09:49:02+00:00\",\"description\":\"Salesforce \u8fde\u63a5 Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u3002\u4f7f\u7528 Apex Connector Framework\uff0c\u7528\u4e8e\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u6570\u636e\u5e76\u5728\u672c\u5730\u5408\u6210\u6570\u636e\u3002 Salesforce Connect \u5728 Salesforce \u5916\u90e8\u5bf9\u8c61\u4e2d\u8868\u793a\u8be5\u6570\u636e\uff0c\u4f7f\u7528\u6237\u548c Lightning Platform \u53ef\u4e0e\u5b58\u50a8\u5728 Salesforce \u5916\u90e8\u7684\u6570\u636e\u65e0\u7f1d\u4ea4\u4e92 \u7ec4\u7ec7\u3002\",\"breadcrumb\":{\"@id\":\"http:\/\/www.ponybai.com\/?p=3745#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"http:\/\/www.ponybai.com\/?p=3745\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"http:\/\/www.ponybai.com\/?p=3745#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"http:\/\/www.ponybai.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Salesforce \u8fde\u63a5\"}]},{\"@type\":\"WebSite\",\"@id\":\"http:\/\/www.ponybai.com\/#website\",\"url\":\"http:\/\/www.ponybai.com\/\",\"name\":\"\u767d\u767d\u968f\u7b14\",\"description\":\"Salesforce\u5b98\u65b9\u8bb2\u5e08\",\"publisher\":{\"@id\":\"http:\/\/www.ponybai.com\/#\/schema\/person\/935c7592c850c65e1e5eba4530dbf883\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"http:\/\/www.ponybai.com\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"zh-Hans\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"http:\/\/www.ponybai.com\/#\/schema\/person\/935c7592c850c65e1e5eba4530dbf883\",\"name\":\"ponybai\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"http:\/\/www.ponybai.com\/#\/schema\/person\/image\/\",\"url\":\"http:\/\/www.ponybai.com\/wp-content\/uploads\/2025\/03\/cropped-Ihsans-WeChatQR.jpg\",\"contentUrl\":\"http:\/\/www.ponybai.com\/wp-content\/uploads\/2025\/03\/cropped-Ihsans-WeChatQR.jpg\",\"width\":248,\"height\":248,\"caption\":\"ponybai\"},\"logo\":{\"@id\":\"http:\/\/www.ponybai.com\/#\/schema\/person\/image\/\"},\"sameAs\":[\"http:\/\/121.37.188.161\"],\"url\":\"http:\/\/www.ponybai.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Salesforce \u8fde\u63a5 - \u767d\u767d\u968f\u7b14","description":"Salesforce \u8fde\u63a5 Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u3002\u4f7f\u7528 Apex Connector Framework\uff0c\u7528\u4e8e\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u6570\u636e\u5e76\u5728\u672c\u5730\u5408\u6210\u6570\u636e\u3002 Salesforce Connect \u5728 Salesforce \u5916\u90e8\u5bf9\u8c61\u4e2d\u8868\u793a\u8be5\u6570\u636e\uff0c\u4f7f\u7528\u6237\u548c Lightning Platform \u53ef\u4e0e\u5b58\u50a8\u5728 Salesforce \u5916\u90e8\u7684\u6570\u636e\u65e0\u7f1d\u4ea4\u4e92 \u7ec4\u7ec7\u3002","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"http:\/\/www.ponybai.com\/?p=3745","og_locale":"zh_CN","og_type":"article","og_title":"Salesforce \u8fde\u63a5 - \u767d\u767d\u968f\u7b14","og_description":"Salesforce \u8fde\u63a5 Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u3002\u4f7f\u7528 Apex Connector Framework\uff0c\u7528\u4e8e\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u6570\u636e\u5e76\u5728\u672c\u5730\u5408\u6210\u6570\u636e\u3002 Salesforce Connect \u5728 Salesforce \u5916\u90e8\u5bf9\u8c61\u4e2d\u8868\u793a\u8be5\u6570\u636e\uff0c\u4f7f\u7528\u6237\u548c Lightning Platform \u53ef\u4e0e\u5b58\u50a8\u5728 Salesforce \u5916\u90e8\u7684\u6570\u636e\u65e0\u7f1d\u4ea4\u4e92 \u7ec4\u7ec7\u3002","og_url":"http:\/\/www.ponybai.com\/?p=3745","og_site_name":"\u767d\u767d\u968f\u7b14","article_published_time":"2023-12-20T09:32:10+00:00","article_modified_time":"2023-11-25T09:49:02+00:00","og_image":[{"width":755,"height":207,"url":"http:\/\/www.ponybai.com\/wp-content\/uploads\/2023\/11\/datasource.png","type":"image\/png"}],"author":"ponybai","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"ponybai","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"7 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"http:\/\/www.ponybai.com\/?p=3745#article","isPartOf":{"@id":"http:\/\/www.ponybai.com\/?p=3745"},"author":{"name":"ponybai","@id":"http:\/\/www.ponybai.com\/#\/schema\/person\/935c7592c850c65e1e5eba4530dbf883"},"headline":"Salesforce \u8fde\u63a5","datePublished":"2023-12-20T09:32:10+00:00","dateModified":"2023-11-25T09:49:02+00:00","mainEntityOfPage":{"@id":"http:\/\/www.ponybai.com\/?p=3745"},"wordCount":762,"publisher":{"@id":"http:\/\/www.ponybai.com\/#\/schema\/person\/935c7592c850c65e1e5eba4530dbf883"},"keywords":["Salesforce \u8fde\u63a5"],"articleSection":["Apex \u5f00\u53d1\u4eba\u5458\u6307\u5357"],"inLanguage":"zh-Hans"},{"@type":"WebPage","@id":"http:\/\/www.ponybai.com\/?p=3745","url":"http:\/\/www.ponybai.com\/?p=3745","name":"Salesforce \u8fde\u63a5 - \u767d\u767d\u968f\u7b14","isPartOf":{"@id":"http:\/\/www.ponybai.com\/#website"},"datePublished":"2023-12-20T09:32:10+00:00","dateModified":"2023-11-25T09:49:02+00:00","description":"Salesforce \u8fde\u63a5 Apex \u4ee3\u7801\u53ef\u4ee5\u901a\u8fc7\u4efb\u4f55 Salesforce Connect \u9002\u914d\u5668\u8bbf\u95ee\u5916\u90e8\u5bf9\u8c61\u6570\u636e\u3002\u4f7f\u7528 Apex Connector Framework\uff0c\u7528\u4e8e\u4e3a Salesforce Connect \u5f00\u53d1\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u3002\u81ea\u5b9a\u4e49\u9002\u914d\u5668\u53ef\u4ee5\u4ece\u5916\u90e8\u7cfb\u7edf\u68c0\u7d22\u6570\u636e\u5e76\u5728\u672c\u5730\u5408\u6210\u6570\u636e\u3002 Salesforce Connect \u5728 Salesforce \u5916\u90e8\u5bf9\u8c61\u4e2d\u8868\u793a\u8be5\u6570\u636e\uff0c\u4f7f\u7528\u6237\u548c Lightning Platform \u53ef\u4e0e\u5b58\u50a8\u5728 Salesforce \u5916\u90e8\u7684\u6570\u636e\u65e0\u7f1d\u4ea4\u4e92 \u7ec4\u7ec7\u3002","breadcrumb":{"@id":"http:\/\/www.ponybai.com\/?p=3745#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["http:\/\/www.ponybai.com\/?p=3745"]}]},{"@type":"BreadcrumbList","@id":"http:\/\/www.ponybai.com\/?p=3745#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"http:\/\/www.ponybai.com\/"},{"@type":"ListItem","position":2,"name":"Salesforce \u8fde\u63a5"}]},{"@type":"WebSite","@id":"http:\/\/www.ponybai.com\/#website","url":"http:\/\/www.ponybai.com\/","name":"\u767d\u767d\u968f\u7b14","description":"Salesforce\u5b98\u65b9\u8bb2\u5e08","publisher":{"@id":"http:\/\/www.ponybai.com\/#\/schema\/person\/935c7592c850c65e1e5eba4530dbf883"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"http:\/\/www.ponybai.com\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"zh-Hans"},{"@type":["Person","Organization"],"@id":"http:\/\/www.ponybai.com\/#\/schema\/person\/935c7592c850c65e1e5eba4530dbf883","name":"ponybai","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"http:\/\/www.ponybai.com\/#\/schema\/person\/image\/","url":"http:\/\/www.ponybai.com\/wp-content\/uploads\/2025\/03\/cropped-Ihsans-WeChatQR.jpg","contentUrl":"http:\/\/www.ponybai.com\/wp-content\/uploads\/2025\/03\/cropped-Ihsans-WeChatQR.jpg","width":248,"height":248,"caption":"ponybai"},"logo":{"@id":"http:\/\/www.ponybai.com\/#\/schema\/person\/image\/"},"sameAs":["http:\/\/121.37.188.161"],"url":"http:\/\/www.ponybai.com\/?author=1"}]}},"_links":{"self":[{"href":"http:\/\/www.ponybai.com\/index.php?rest_route=\/wp\/v2\/posts\/3745","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.ponybai.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.ponybai.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.ponybai.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.ponybai.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3745"}],"version-history":[{"count":1,"href":"http:\/\/www.ponybai.com\/index.php?rest_route=\/wp\/v2\/posts\/3745\/revisions"}],"predecessor-version":[{"id":3747,"href":"http:\/\/www.ponybai.com\/index.php?rest_route=\/wp\/v2\/posts\/3745\/revisions\/3747"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.ponybai.com\/index.php?rest_route=\/wp\/v2\/media\/3746"}],"wp:attachment":[{"href":"http:\/\/www.ponybai.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3745"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.ponybai.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3745"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.ponybai.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3745"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}