Salesforce的Prefix和Suffix

行家一出手,就知有没有。
判断一个Salesforce从业者是不是老手的一个标准,
就是能不能对Governor Limit,Sobject的Prefix和Suffix倒背如流。

Governor Limit之前已经介绍过了,
Salesforce说了,当你觉得Governor Limit限制了你,那么意味着你的业务流程没有达到最优。

那么Prefix是什么呢。
Prefix就是俗称的3位码。
每个SObject都有一个3位编码,每一条该SObject的数据的头三位,都是以此3位编码开头。
如果把三位编码放在salesforce的url后面,可以快速的访问SObject的ListView页面。
https://[your instance name].salesforce.com/[prefix]

标准SObject的prefix都是固定的,
比如,Account一定是001
Contact一定是003
Lead一定是00Q
而自定义SObject就没有固定的编码了,但都以aXX开头。

Salesforce提供了一本速查手册
列出了所有标准SObject的prefix。
如果想得到一个全体SObject的花名册,可以去实现一下tooling api。
所有标准的,自定义的,还有不可见的SObject的Prefix都列出来了。

那么,Suffix呢,大家最熟悉的莫过于”__c”,
所有的自定义SObject的Suffix,所有自定义字段的Suffix,都是它。
那么除了__c之外,还有哪些呢。
国外大神总结在此
防丢失转载如下:

__c Custom Object or Custom field
__r Custom relationship field as used in a SOQL query to traverse the relationship
__ka KnowledgeArticle
__kav KnowledgeArticleVersion
__Feed Article Type Feed or Custom Object Feed
__ViewStat KnowledgeArticleViewStat
__VoteStat KnowledgeArticleVoteStat
__DataCategorySelection Article Type__DataCategorySelection
__x External Object
__xo Salesforce-to-Salesforce (S2S) spoke/proxy object
__mdt Custom Metadata Type
__Share Custom object sharing object
__Tag Salesforce Tags
__History Field History Tracking for Custom Objects
__pc Custom Person Account Field
__pr Used for traversing custom Person Account relationship fields
__hd Historical Data
__hqr, __hst Start/End of the Datetime range
__b BigObject
__latitude__s Geolocation Latitude Coordinate
__longitude__s Geolocation Longitude Coordinate
__e Platform Events – a.k.a. EventBus event (Winter ’17 pilot)
__p Custom Person Object (Spring ’15 pilot)

————By Daniel Ballinger

Salesforce的Field Id与URL Hack

默认值是个好东西。
别说是普通用户,就算是开发者,在面对满满一屏的空白字段时,也是要不禁皱皱眉头的。

设置得当的默认值可以极大的提高业务流畅度与用户满意度。
反之,每次都不得不改的默认值,会带来加倍的苦恼。(甚至会惹恼用户)

当然,Salesforce提供了字段的Default Value功能。
我们可以为大部分字段类型设置默认值。

Default Value必须为公式,并且不能使用其他字段的值,只可以使用预设的系统变量和环境变量。
因为这样的限制,使得我们在使用自定义button或者link跳转到Object标准编辑画面的时候,无法根据情况显示不同的默认值。

举个例子,比如Case上面有个字段用来储存Case的类型,然后在Case的详细画面上放置一个自定义Button,这个button用来新建一个order。
为了提高Agent的工作效率,弹出order的新建页面之后,我们希望order的类型是Case上所选择的值,并且与Case关联同一个Account。

这种场景下,字段的Default Value已经无法满足需求。
我们需要使用一种Salesforce并不提倡的技巧———-URL Hack。

URL Hack就是通过在Salesforce的URL上加上额外的参数,来实现我们的需求。
其中一种技巧,就是给Standard Pagelayout赋值。

那么让我们来看看URL Hack到底是什么样子。
首先,点击Case标签里的新建Button。
跳转到Case的新建画面之后,观察地址栏的URL如下
无RecordType: https://[InstanceName].salesforce.com/500/e?retURL=%2F500%2Fo
有RecordType: https://[InstanceName].salesforce.com/500/e?retURL=%2F500%2Fo&RecordType=0126F000000v348&ent=Case

其中,500代表Case,每个Object都有一组三位码,同时也意味着所有的数据的Id都是由这三位码开头。
e代表编辑页面。 500/e代表新建数据,500XXXXXXXXX/e代表修改既存数据。
retURL代表回调页面,当点击取消Button之后,会返回到指定的页面。在例子中,%2F代表“/”,所以会返回500/o。
有RecordType的URL多了一个RecordType参数,值0126F000000v348就是recordType的Id。

所以,RecordType以外的字段,我们也可以通过同样的方式进行赋值。

首先,与RecordType不同的是,我们需要用的并不是字段的API名,而是Field ID。
如果不使用工具的话,Field Id并没有快捷的查看方式,所以应急的话,可以直接打开编辑画面,然后用浏览器的F12开发者工具直接查看。

标准字段是字母+数字的形式,自定义字段则是以00N开头的ID。
就拿上面的例子来说,如果将“123456”赋给Account Number,URL应写为
https://[InstanceName].salesforce.com/500/e?acc5=123456&retURL=%2F500%2Fo

如果是Lookup字段的话,上面那样写就行不通了,要在Field Id后面加上_lkid后缀
标准字段的话,我们以Account的Parent Account为例
https://[InstanceName].salesforce.com/001/e?acc3_lkid=001XXXXXXXXX

自定义字段的话,Lookup字段还需要在00N前加上CF
https://[InstanceName].salesforce.com/001/e?CF00NXXXXXXXXX_lkid=001XXXXXXXXX

有的时候只写lkid结尾的参数看起来并不管用,在编辑页面上,Lookup字段仍然是空白的,但实际上保存之后是已经被赋值的。
如果遇到这种现象,就要加额外加一个参数
https://[InstanceName].salesforce.com/001/e?acc3_lkid=001XXXXXXXXX&acc3=RecordName
https://[InstanceName].salesforce.com/001/e?CF00NXXXXXXXXX_lkid=001XXXXXXXXX&CF00NXXXXXXXXXX=RecordName

最后记住,一定要养成随时设置retURL的好习惯。

上述所有的值在Custome Button或者Link中,都可以用表达式替换,比如
00NXXXXXXXXXXXX={!Case.Status}等。

如果想了解URL Hack还能做哪些事情,之后会写一篇专访。

Salesforce的TestClass

虽然Salesforce从业人员应该尽量使用标准功能来实现需求,
但仍不可避免的要用到代码。

标准功能的话,Salesforce会自己负责质量(虽然Bug频发。。。),
对于自定义功能,Salesforce则制定了质量标准,
比如,如果总体代码覆盖率不到75%,无法Deploy。

一般的Apex Code,就像那些出现在Trigger里,出现在Controller里的代码,
测试类都很好写,就按照
1. 准备测试数据
2. 执行业务逻辑
3. 断言执行结果
按套路打就行了。

除此之外,另一些apex code,需要特别的测法。
Salesforce作为CRM系统,无法避免的要与其他系统进行数据交互。
大部分情况,我们只需要把salesforce的标准集成文档和权限已经配置妥当的账号,提供给对方就好,
但是,当我们需要将业务封装起来的时候,就需要自己建WebService。
况且,Salesforce主动调用外部系统接口的时候,又要区分是Rest还是SOAP。

对于Apex Rest Callout可以使用下面三种方式(代码示例点击链接)
1. HttpCalloutMock
比较常见的写法,有极大的可拓展性(毕竟连响应结果都要自己Code进去)。
2. StaticResourceCalloutMock
LoadData是一对好兄弟,都是在Test Class中利用StaticResource,可以很好的做到测试数据与测试代码分离。
3. MultiStaticResourceCalloutMock
StaticResourceCalloutMock的复数URL版。

对于Apex SOAP Callout,Salesforce也提供了一种方式。
1. WebServiceMock

以上是Callout的测试类写法,原理均是构造一个Mock类,然后提供一个假的response。
你总不能期待每次跑测试类的时候,让人家集成方的服务器真的去响应你吧?

那么Callin呢。
首先,处理Callin的Apex Class一定是WebService。
WebService也分为Rest Webservice和SOAP WebService。
对于Rest WebService,Salesforce并没有提供类似与Mock类的工具类,所以我们直接去构造上下文环境。
在实际情况种,上下文环境是由系统进行构造的,这里我们代替系统。

        .................
        RestRequest request = new RestRequest();
        request.requestURI = 'https://[InstanceName].salesforce.com/services/apexrest/xxxxxx/';
        request.httpMethod = 'GET';
        RestContext.request = request;
        /** Call your WebService like below **/
        MyWebService.MyFoo();
        .................

对于SOAP Webservice就简单多了。因为不需要处理上下文环境的参数等内容,直接处理方法中的参数就好,
所以就当作普通的Apex Code处理就可以,不需要特殊的操作。

除此之外。

虽然目前我还没见有人用过,但是Salesforce还是提供了一个Stub接口,用来做Mock框架。
它的好处是可以实现单元测试的完全解耦,对各个小模块进行单独的测试。
就目前市面上各个大公司的状况来看。。。能好好写个断言就不错了。(捂脸)(尴尬)

Salesforce的Rollback与SavePoint

之前对于Salesforce的RollBack机制没有进行深入的思考。
那天,闲来无事,有人问了我一个问题。

问说,下面的代码在匿名快执行完之后插进去了几条数据。

ObjectA__c objA = new ObjectA__c();
objA.Name = "TestA";
insert objA;

List<ObjectB__c> objBList = new List<ObjectB__c>
for(Integer i = 0; i < 200; i++) {
    ObjectB__c objB = new ObjectB__c();
    objB.Name = "TestB" + i;
    insert ojbB;
}

我大概扫了一眼,问的是Governor Limit嘛,答案是150,送分题,不谢。
但又隐约的觉得不大对劲,既然拿来问我,肯定有坑。
立马打开Developer Console试了一下。
看到了结果倒吸了一口冷气。。。。。。。。0,一条都没插进去。全都RollBack了?

咦?不对啊,那一定是匿名块做了特殊的设置。试试Controller。
要不然我以前在Controller里写个什么劲儿的SavePoint。
VF:

<apex:page controller="TestRollbackClass">
    <apex:form>
    	<apex:commandButton action="{!save}" value="Button"/>  
    </apex:form>
</apex:page>

Apex Class:

public class TestRollbackClass {

    public void save() {     
        ObjectA__c oa = new ObjectA__c();
        oa.Name = 'TestA';
        insert oa;

	ObjectB__c ob = new ObjectB__c();
        ob.Name = 'testB';
        insert ob;
    }
}

我在ObjectB上加了一个Validation Rule,保证新建ObjectB记录的一定会报错。
这次总该如预期一般只有ObjectA被插进去了吧。

结果一跑,唉? 还是都RollBack了?
瞬间怀疑人生。既然都自动RollBack了,那我以前为什么必须写SavePoint?
去翻了下Release Note,也没找到相关的变动。

没办法,一定是写法问题,我试着按照Coding Standard写一次

public class TestRollbackClass {

    public void save() {
	ObjectA__c oa = new ObjectA__c();
	oa.Name = 'Test2';
        insert oa;
        try {
            ObjectB__c ob = new ObjectB__c();
        	ob.Name = 'test1';
        	insert ob;
        } catch(Exception e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getMessage()));
        }
    }
}

再次执行,第一条数据ObjectA居然插进去了。
我瞬间明白了点什么。

Salesforce的机制应该是这样的,
如果Transaction被异常中断,则Rollback全部的数据变动。
如果异常被捕获,则已经正常commit的数据变动会落实。出错的部分无效。

按照开发潜规则,应该展现给User更Friendly的错误信息,而不是可怕的Trace Log信息。
所以我以前的公司要求捕获所有可能的异常并替换成友好的错误信息。
这样的话,Transaction一定不会被异常中断,所以自动全部Rollback的机制从来没有启用过。
那么为了防止在出错时插入脏数据,Salesforce提供了SavePoint。
用法如下:

public class TestRollbackClass {

    public void save() {
        SavePoint sp = Database.setSavepoint();
		ObjectA__c oa = new ObjectA__c();
		oa.Name = 'Test2';
        insert oa;
        try {
            ObjectB__c ob = new ObjectB__c();
        	ob.Name = 'test1';
        	insert ob;
        } catch(Exception e) {
            Database.rollback(sp);
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getMessage()));
        }
    }
}

如果ObjectB插入出错,已insert的ObjectA也会回滚。达到目的。

Salesforce的字段类型转换

如果维护/接手别人的烂摊子,
有很大几率碰到字段类型转换问题。

Salesforce的字段类型转换并不是100%安全。
如果符合下列规则,则会丢失所有数据。

Date -> Datetime
DateTime -> Date
Any -> Percent
Any -> Number
Any -> Currency
Checkbox -> Any
MultiPicklist -> Any
Any -> MultiPicklist
PickList -> multiPickList value retains if exsited in multipicklist
AutoNumber -> Any(Exclude Text)
Any (exclude Text) -> AutoNumber
Text -> PickList
TextArea(Long) -> Any(exclude Email, phone, Text, Text Area, URL)

常规操作顺序,
应该先在sandbox上演练一下,
确认结果与预期一致。
然后,
先将生产环境的数据备份,
再在生产环境上进行实际操作。

官方文档
https://help.salesforce.com/articleView?id=notes_on_changing_custom_field_types.htm&type=5

Salesforce关于Debug

做Salesforce开发,最让人头疼的事情,莫过于Debug。

不同于Java开发,已经有了一系列成熟的Debug工具,
Salesforce开发者最常用的手段只有输出log,就如同Javascript开发一样。
不过随着现代浏览器的兴起,就算是Javascript开发也可以debug了。

对于一个老手来说,添加System.debug()然后查log,虽然已经足够。
但是,面对巨型transaction的时候,就要面临Debug Log的限制
1. log总体大小不能超过3MB,否则随机截断(不管官网怎么说,我觉得是随机的,囧)
2. log只能存留七天
3. 15分钟内产生的log不能超过250MB

为了回避此问题,Salesforce提供了Class和Trigger的Debug Log级别自定义功能。官网点此
启用这个功能,可以在Class和Trigger的详细页面,找到TraceFlag,在此设定需要的Debug log级别,(一般全开最详细,或者全关)
自定义完成后,transaction中class和trigger产生log时的级别设定,就会覆盖默认的debug log级别设定。
使用此功能可以极大的减少不需要的log输出,从而避免被随机截断。

但是,System.debug必然不是万能的。

所以在DeveloperConsole中,Salesforce给开发者提供了CheckPoint功能,注意,不是BreakPoint,是CheckPoint。
Checkpoint不同于BreakPoint那样可以互动。只提供了信息查看功能。
就是说,在代码中打上Checkpoint之后,程序执行并不会暂停,而是执行到结束,但Salesforce会将Checkpoint标记位置的内存状态保留下来供开发者查看。
需要值得注意的是,Checkpoint全局只能打5个。

如果代码真的复杂到checkpoint和Debug log都无法搞定,
Salesforce提供了一个真正的Debug工具——Apex Debugger
有了Apex Debugger,Salesforce开发者也可以像Java程序员一样debug了。
不过越强大的武器,限制越多。
Apex Debugger只提供了一个免费的License,并且是全Org通用,就说Production和旗下所有sandbox合起来同时只能有一个人用。

不管使用哪种Debug方式,都应该从设计伊始就避免设计出如此巨大的自定义功能。

Salesforce的Sharing Rule中Criteria Rule如何判断空值

某日,客户提了一个需求,
说想把某个字段是非空的数据Share给另一个部门。

看起来是个很简单的需求,
就是一个Sharing Rule的事儿。

然后,立马新建了一个Sharing criteria rule,
设定条件为[ 字段 not equals to 空值 ],保存。唉?报错了。
那么空格呢?也不行。

Error: Empty value

难道,Sharing Rule不能判断空值?
正在百思不得其解之时,经高人指点,说空值的位置写成[“”]就可以了。

赶紧试了一下,果然成功了。

保存后的效果是这样的。

Salesforce如何用Import Wizard将空值插入PicklistValue字段

如果Picklist Value没有定义为必填字段。
则多出一个–None–选项可以选,
在DB里存为空。

我们知道Dataloader默认会无视所有值为空的字段,
除非我们在设置里将[Insert Null Values]选中。

但是在Import Wizard中并没有提供类似的选项。
如果尝试将字段的值留空,一样会被无视,空格等一切空白字符也会被无视。

解决方法是,将想设置成空的字段的值改为[#N/A]。
之后再次导入就可以了。

Salesforce如何查看RecordType的Assignment

我发现Salesforce只提供了Profile-RecordType-Layout三角关系(Assignment)的查看页面。
并没有提供查看某个RecordType都有哪些Profile可以创建的视角。
只能一个一个Profile去点开。

思来想去,如果不用第三方工具的话,只能直接去搜Metadata了。

首先用ForceIDE或者其他方式获取Profile的完整MetaData。

然后,打开全局检索,勾选启用正则表达式。
然后用下面的字符串搜索全部的Profile
<recordType>[ObjectName].[RecordType Name]</recordType>\n.*true

搜出的结果就是能创建这个RecordType的Profile了。

也许将来能够像FLS一样,以recordType的视角直接查看Profile的Assignment情况。

Salesforce的Tag(标签)-一天一个标准功能系列

这两天有小盆有提到了一个需求,
说客户一直在用MacBook,
MacOS提供了一个标签功能,客户可以将一组散落在不同位置的,不同类型的文件,打上同一个标签,
之后可以很方便的管理同一标签的文件。
所以客户也想在Salesforce里面使用这种功能。
问我怎么办才好。

我问他,“你想怎么实现呢”
“我想,首先要确认都有哪些obj需要被打标签”
“很好,然后呢”
“然后,我可以在这些obj上都加上一个自定义字段,叫Tag,然后在所有的Layout上把这个Tag字段放出来。”
“唔。。。可以实现,但是,用户怎么查看都有哪些数据被打了相同的Tag呢?而且,如果多个用户都需要打Tag,怎么保证互不干扰,并且互相保密呢?”
“啊。。。。我想想,查看的话,我可以写一个VF,通过查询把所有的表都查一遍,多个用户就麻烦了。。。我想想。。。”
(过了半个小时)
我问道,“有想法了没?”
“有了!我不加字段了,那样不科学!”
“很好,那你想怎么做呢?”
“首先,我建一张表叫Tag__c。然后关联所有需要打Tag的obj。之后Sharing Rule设置成private。查看Tag的时候,用ListView进行过滤,把所有相同Tag名字的数据都显示出来。(得意)”
“那打标签的动作怎么解决。就是打算如何在数据上点一下就加上一个标签。”
“我打算用Quick Action或者自定义Button,将它们添加到所有的Layout上,这样在Detail画面直接点一下就能打上标签了。”
“那如果有个标签我想给所有人看呢?(坏笑)”
“那样的话我得用Sharing Rule,将Share to All Flag为True的数据Share给所有人。”
“累不?”
“累。。。。”
“你听说过标准Tag功能吗?”
“我就知道!!!”

标签,无论是在现实世界还是计算机世界,都是管理资源的良好手段。
所以Salesforce也原生提供了Tag功能,我们要做的,就是将其开启就好。
官方文档,点击这里

开启之后,查看标签的效果是这样的

点击[Personal Tags]之后

开启之后,在数据的详细画面上,可以点击右上的[Edit Tag]添加标签。

不过,Tag也不是无限打的,是有个数限制的

开启的方法为,
Setup -〉Build -〉Customize -〉 Tags -〉Tag Setting
开启Personal Tag之后,可以选择是否在Report等之上使用Tag,与显示Tag功能的PageLayout。

需要留意的是,Salesforce官方文档提到了两种Tag,分别是Personal Tag和Public Tag,
但是DE只能开启一种Personal Tag。
只有真正的Production才能开启Public Tag。

有了这个功能,用户可以跨越表关联,方便的管理一组数据了。

有同学质疑我的“一天一个”,我的意思是。。。。我花了一天时间才能写一个的。。。。