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情况。