Salesforceのファイルのオブジェクト構成とApexの書き方


この記事は Salesforce Platform Advent Calendar 2020 - Qiita 第14日目の投稿および
Salesforce 開発者向けブログ投稿キャンペーンへのエントリー記事です。

はじめに

Salesforceのファイルについてまとめてみました

・「メモ&添付ファイル」と「ファイル」の違い
・ContentDocument と ContentVersion のオブジェクト構成
・レコード添付時のオブジェクト構成の違い
・レコードIDから ContentVersion データ一覧の取得方法
・Apex でのファイル insert の違い
・Apex でファイルをコピーする方法
・ファイルをダウンロードするURLを生成

「メモ&添付ファイル」と「ファイル」の違い

まず、レコードにファイルを添付するには以下の2種類の方法があります。
Classic 世代の古くから存在する「メモ&添付ファイル」と、 LightningExperience 世代の新しい「ファイル」の2種類が存在します。

ファイルアップロード時の格納場所の違いについて

アップロードするインターフェースとアップロードする場所によって格納されるオブジェクトが異なります。

インターフェース アップロード方法 格納されるオブジェクト
Classic メモ&添付ファイル Attachment
Classic ファイル ContentDocument および ContentVersion
Lightning Experience メモ&添付ファイル ContentDocument および ContentVersion
Lightning Experience ファイル ContentDocument および ContentVersion

「メモ&添付ファイル」から Attachment に格納というイメージではなく、以下の Classic のインターフェースの際に Attachment に格納されるイメージかと思います。

ContentDocument と ContentVersion のオブジェクト構成

基本的には Classic では「Attachment」LightningExperience では「ContentDocument と ContentVersion」となっているため、今後は以下の Content 関連が重要となってきます。

【参考】Apex 開発者ガイド:コンテンツのオブジェクト

ContentDocument ではファイルのコンテンツ情報を保持し、 ContentVersion ではバージョン情報を保持します。ファイルの実データは ContentVersion 内で保持しています。
ライブラリを使用する場合は、 ContentWorkspace との関連を ContentWorkspaceDoc で保持します。
レコードへの関連は ContentDocumentLink で保持します。

Attachment の時とのレコード添付時の構成の違い


Attachment の際はシンプルで、オブジェクトレコードの関連やファイルデータの実態は Attachment オブジェクト内で全て完結します。


ContentDocument および ContentVersion の際は少し複雑で、オブジェクトレコードの関連は ContentDocumentLink で行い、ファイルデータの実態は ContentVersion の VersionData で保持します。

レコードIDから ContentVersion データ一覧の取得方法

レコードIDから ContentVersion データの一覧の取得方法は2パターン

パターン①: ContentDocumentLink から対象レコードIDを指定して取得

SELECT ContentDocument.LatestPublishedVersionId FROM ContentDocumentLink WHRERE LinkedEntityId = '............'

パターン②:対象オブジェクトの CombinedAttachment のサブクエリから取得

SELECT Id, (SELECT Id, Title FROM CombinedAttachments) FROM Test__c WHRERE Id = '............'

/*
サブクエリで取得したIdを指定することで以下の ContentDocument が取得できるため、
LatestPublishedVersionId で ContentVersion データのIDが取得できる
*/

SELECT LatestPublishedVersionId FROM ContentDocument WHERE Id IN :idList

ただし、②では sobject.CombinedAttachments のようにSObject型では取得できないため、 account.CombinedAttachments のようにオブジェクトを指定しないと取得できないみたいなのであまり汎用的に使用できません。

Apex でのファイル insert の違い

Attachment 場合

Attachment att = new Attachment();
att.ParentId = '............'; // レコードへの関連
att.Name = 'test.csv'; // ファイル名
att.Body = Blob.valueOf('test'); // ファイルデータ
insert att;

ContentDocument および ContentVersion 場合

ContentVersion cv = new ContentVersion();
cv.ContentLocation = 'S'; // ファイルはSalesforce上に保存
cv.PathOnClient = 'test.csv'; // ファイル名
cv.Title = 'テスト'; // ファイルタイトル
cv.VersionData = Blob.valueOf('test'); // ファイルデータ
insert cv;

// レコードへの関連
ContentDocumentLink cde = new ContentDocumentLink();
cde.ContentDocumentId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id].ContentDocumentId; // コンテンツ情報
cde.LinkedEntityId = '............'; // レコードへの関連
cde.ShareType = 'V'; // ファイル権限
insert cde;

ContentDocument は直接 insert 不可
エラー「DML operation Insert not allowed on ContentDocument」
ContentVersion を insert すると自動で ContentDocument が insert される仕様のようです。

Apex でファイルをコピーする方法

ファイルの実態もコピーする場合

// コピー元ファイル
ContentVersion origin = [SELECT Id, Title, PathOnClient, ContentDocumentId, VersionData FROM ContentVersion WHERE Id = '............'];

// ファイルを新規作成
ContentVersion cv = new ContentVersion();
cv.ContentLocation = 'S'; // ファイルはSalesforce上に保存
cv.PathOnClient = origin.PathOnClient; // ファイル名
cv.Title = origin.Title; // ファイルタイトル
cv.VersionData = origin.VersionData; // ファイルデータ
insert cv;

// レコードへの関連
ContentDocumentLink cde = new ContentDocumentLink();
cde.ContentDocumentId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id].ContentDocumentId; // コンテンツ情報
cde.LinkedEntityId = '............'; // レコードへの関連
cde.ShareType = 'V'; // ファイル権限
insert cde;

ファイルの実態はコピーせず、ファイルの関連先のみを増やす場合

// コピー元ファイル
ContentVersion origin = [SELECT Id, Title, PathOnClient, ContentDocumentId, VersionData FROM ContentVersion WHERE Id = '............'];

// レコードへの関連
ContentDocumentLink cde = new ContentDocumentLink();
cde.ContentDocumentId = origin.ContentDocumentId; // コンテンツ情報
cde.LinkedEntityId = '............'; // レコードへの関連
cde.ShareType = 'V'; // ファイル権限
insert cde;

ファイルの関連先のみを増やす場合は、同じコンテンツ情報を共有しているため、「新しいバージョンをアップロード」で別ファイルデータにバージョンを上げた際にも両方のオブジェクトのファイルデータに同期されます。

ファイルをダウンロードするURLを生成

以下URLでファイルダウンロードが可能
ContentVersion のIDをパラメータに設定し、URLにアクセスするだけでそのファイルをダウンロードすることが可能です。
https://xxxxxxx.my.salesforce.com/sfc/servlet.shepherd/version/download/{!ContentVersionId}

まとめ

Salesforceには、「メモ&添付ファイル」と「ファイル」があり、それぞれの違いがよくわからなかったり、また Attachment と Content 関連の仕組みが全然違ったりするため、備忘録も兼ねて今回はファイル関連についてまとめました。