如何在 Azure 和 DB 中撰寫儲存的程式、觸發程式和使用者Cosmos函數

適用于 :SQL API

Azure Cosmos DB 提供語言整合的 JavaScript 交易式執行,可讓您在UDF中撰寫儲存的程式、觸發程式及使用者 (函數) 。 在 Azure SQL DB 中Cosmos API 時,您可以使用 JavaScript 語言定義儲存的程式、觸發程式和 UDF。 您可以在 JavaScript 中撰寫邏輯,然後于資料庫引擎內執行。 您可以使用 Azure 入口網站、Azure Cosmos DB中的 JavaScript語言整合查詢 API 和 Cosmos DB SQL API 用戶端 SDK 來建立並執行觸發程式、儲存的程式和 UDF。

若要呼叫儲存的程式、觸發程式及使用者定義的函數,您必須註冊它。 若要詳細資訊,請參閱如何使用 Azure 中的儲存程式、觸發程式、使用者定義函數Cosmos DB

注意

針對已分割容器,在執行儲存程式時,必須在要求選項中提供分區鍵值。 儲存的程式一直以分區鍵為範圍。 儲存的程式不會看到具有不同分區鍵值的專案。 這也適用于觸發者。

提示

Cosmos使用儲存的程式、觸發程式和使用者定義函數來部署容器。 詳細資訊請參閱使用伺服器端功能Cosmos建立 Azure 資料庫容器。

如何撰寫儲存的程式

儲存的程式是使用 JavaScript 撰寫,他們可以在 Azure 或容器內建立、更新、讀取、查詢Cosmos專案。 儲存的程式會依集合進行註冊,並可在該集合中顯示的任何檔或附件上執行。

以下是一個簡單的儲存程式,會回復「Hello World」回應。

var helloWorldStoredProc = {
    id: "helloWorld",
    serverScript: function () {
        var context = getContext();
        var response = context.getResponse();

        response.setBody("Hello, World");
    }
}

上下文物件提供在 Azure Cosmos DB 中執行之所有作業的存取權,以及要求和回應物件的存取權。 在這種情況下,您可以使用回應物件來設定要寄回用戶端的回應本文。

撰寫完成後,儲存的程式必須註冊于集合中。 若要深入瞭解,請參閱如何在 Azure和 DB Cosmos程式。

使用儲存的程式建立專案

當您使用儲存程式建立專案時,專案會插入 Azure Cosmos容器,並會返回新建立專案的識別碼。 建立專案是非同步作業,取決於 JavaScript 回呼函數。 回呼函數有兩個參數:一個用於錯誤物件,一個用於作業失敗,另一個則用於傳回值;在此案例中,已建立的物件。 在回檔中,您可以處理例外或引發錯誤。 如果系統未提供回撥功能且發生錯誤,Azure Cosmos DB 執行時間會傳回錯誤。

儲存的程式也包括設定描述的參數,這是布林值。 當參數設為 true 且描述遺失時,儲存的程式會引發例外。 否則,儲存程式的其餘部分會繼續執行。

下列範例儲存程式會以一組新的 Azure Cosmos 專案做為輸入,將其插入 Azure Cosmos 容器,並返回插入的專案計數。 在此範例中,我們利用Quickstart .NET SQL API 中的 ToDoList 範例

function createToDoItems(items) {
    var collection = getContext().getCollection();
    var collectionLink = collection.getSelfLink();
    var count = 0;

    if (!items) throw new Error("The array is undefined or null.");

    var numItems = items.length;

    if (numItems == 0) {
        getContext().getResponse().setBody(0);
        return;
    }

    tryCreate(items[count], callback);

    function tryCreate(item, callback) {
        var options = { disableAutomaticIdGeneration: false };

        var isAccepted = collection.createDocument(collectionLink, item, options, callback);

        if (!isAccepted) getContext().getResponse().setBody(count);
    }

    function callback(err, item, options) {
        if (err) throw err;
        count++;
        if (count >= numItems) {
            getContext().getResponse().setBody(count);
        } else {
            tryCreate(items[count], callback);
        }
    }
}

陣列做為儲存程式的輸入參數

在 Azure 入口網站中定義儲存程式時,輸入參數會一直以字串形式傳送至儲存的程式。 即使您傳遞字串陣列做為輸入,該陣列會轉換成字串並送到儲存的程式。 若要處理這個問題,您可以在儲存的程式內定義函數,將字串剖析為數組。 下列程式碼顯示如何將字串輸入參數剖析為數組:

function sample(arr) {
    if (typeof arr === "string") arr = JSON.parse(arr);

    arr.forEach(function(a) {
        // do something here
        console.log(a);
    });
}

儲存程式內的交易

您可以使用儲存的程式,在容器內的專案上執行交易。 下列範例使用虛擬足球遊戲 App 中的交易,在單一作業中,在兩個團隊之間交易玩家。 儲存的程式會嘗試讀取兩個 Azure Cosmos每個對應到以引數傳遞之播放者 ID 的專案。 如果找到這兩個玩家,則儲存的程式會交換其團隊來更新專案。 如果過程中發生任何錯誤,儲存程式會引發 JavaScript 例外,隱含中止交易。

// JavaScript source code
function tradePlayers(playerId1, playerId2) {
    var context = getContext();
    var container = context.getCollection();
    var response = context.getResponse();

    var player1Document, player2Document;

    // query for players
    var filterQuery =
    {
        'query' : 'SELECT * FROM Players p where p.id = @playerId1',
        'parameters' : [{'name':'@playerId1', 'value':playerId1}] 
    };

    var accept = container.queryDocuments(container.getSelfLink(), filterQuery, {},
        function (err, items, responseOptions) {
            if (err) throw new Error("Error" + err.message);

            if (items.length != 1) throw "Unable to find both names";
            player1Item = items[0];

            var filterQuery2 =
            {
                'query' : 'SELECT * FROM Players p where p.id = @playerId2',
                'parameters' : [{'name':'@playerId2', 'value':playerId2}]
            };
            var accept2 = container.queryDocuments(container.getSelfLink(), filterQuery2, {},
                function (err2, items2, responseOptions2) {
                    if (err2) throw new Error("Error" + err2.message);
                    if (items2.length != 1) throw "Unable to find both names";
                    player2Item = items2[0];
                    swapTeams(player1Item, player2Item);
                    return;
                });
            if (!accept2) throw "Unable to read player details, abort ";
        });

    if (!accept) throw "Unable to read player details, abort ";

    // swap the two players’ teams
    function swapTeams(player1, player2) {
        var player2NewTeam = player1.team;
        player1.team = player2.team;
        player2.team = player2NewTeam;

        var accept = container.replaceDocument(player1._self, player1,
            function (err, itemReplaced) {
                if (err) throw "Unable to update player 1, abort ";

                var accept2 = container.replaceDocument(player2._self, player2,
                    function (err2, itemReplaced2) {
                        if (err) throw "Unable to update player 2, abort"
                    });

                if (!accept2) throw "Unable to update player 2, abort";
            });

        if (!accept) throw "Unable to update player 1, abort";
    }
}

儲存程式內的邊界執行

以下是大量將專案大量導入到 Azure Cosmos程式的範例。 儲存的程式會檢查 來自 的布林值,然後使用每個儲存程式調用中插入的專案計數來追蹤及繼續批次的進度,以處理綁定 createDocument 執行。

function bulkImport(items) {
    var container = getContext().getCollection();
    var containerLink = container.getSelfLink();

    // The count of imported items, also used as current item index.
    var count = 0;

    // Validate input.
    if (!items) throw new Error("The array is undefined or null.");

    var itemsLength = items.length;
    if (itemsLength == 0) {
        getContext().getResponse().setBody(0);
    }

    // Call the create API to create an item.
    tryCreate(items[count], callback);

    // Note that there are 2 exit conditions:
    // 1) The createDocument request was not accepted.
    //    In this case the callback will not be called, we just call setBody and we are done.
    // 2) The callback was called items.length times.
    //    In this case all items were created and we don’t need to call tryCreate anymore. Just call setBody and we are done.
    function tryCreate(item, callback) {
        var isAccepted = container.createDocument(containerLink, item, callback);

        // If the request was accepted, callback will be called.
        // Otherwise report current count back to the client,
        // which will call the script again with remaining set of items.
        if (!isAccepted) getContext().getResponse().setBody(count);
    }

    // This is called when container.createDocument is done in order to process the result.
    function callback(err, item, options) {
        if (err) throw err;

        // One more item has been inserted, increment the count.
        count++;

        if (count >= itemsLength) {
            // If we created all items, we are done. Just set the response.
            getContext().getResponse().setBody(count);
        } else {
            // Create next document.
            tryCreate(items[count], callback);
        }
    }
}

使用儲存的程式同步處理等待

以下是使用 async-await 與承諾使用說明程式函數的儲存程式範例。 儲存的程式查詢專案並取代它。

function async_sample() {
    const ERROR_CODE = {
        NotAccepted: 429
    };

    const asyncHelper = {
        queryDocuments(sqlQuery, options) {
            return new Promise((resolve, reject) => {
                const isAccepted = __.queryDocuments(__.getSelfLink(), sqlQuery, options, (err, feed, options) => {
                    if (err) reject(err);
                    resolve({ feed, options });
                });
                if (!isAccepted) reject(new Error(ERROR_CODE.NotAccepted, "queryDocuments was not accepted."));
            });
        },

        replaceDocument(doc) {
            return new Promise((resolve, reject) => {
                const isAccepted = __.replaceDocument(doc._self, doc, (err, result, options) => {
                    if (err) reject(err);
                    resolve({ result, options });
                });
                if (!isAccepted) reject(new Error(ERROR_CODE.NotAccepted, "replaceDocument was not accepted."));
            });
        }
    };

    async function main() {
        let continuation;
        do {
            let { feed, options } = await asyncHelper.queryDocuments("SELECT * from c", { continuation });

            for (let doc of feed) {
                doc.newProp = 1;
                await asyncHelper.replaceDocument(doc);
            }

            continuation = options.continuation;
        } while (continuation);
    }

    main().catch(err => getContext().abort(err));
}

如何撰寫觸發

Azure Cosmos DB 支援觸發前和觸發後。 在修改資料庫專案之前先執行預觸發程式,而後觸發程式在修改資料庫專案之後執行。 觸發程式不會自動執行,因此必須針對您想要執行的每個資料庫作業指定觸發程式。 定義觸發程式之後,您應該使用Azure 和 DB SDK 來註冊Cosmos觸發程式。

觸發前

下列範例顯示如何使用預先觸發來驗證所建立之 Azure Cosmos專案的屬性。 在此範例中,我們利用Quickstart .NET SQL API 中的ToDoList 範例,將時間戳記屬性新增到新新增的專案 ,如果不包含時間戳記屬性。

function validateToDoItemTimestamp() {
    var context = getContext();
    var request = context.getRequest();

    // item to be created in the current operation
    var itemToCreate = request.getBody();

    // validate properties
    if (!("timestamp" in itemToCreate)) {
        var ts = new Date();
        itemToCreate["timestamp"] = ts.getTime();
    }

    // update the item that will be created
    request.setBody(itemToCreate);
}

預觸發器不能有任何輸入參數。 觸發中的要求物件是用來操作與作業相關聯的要求訊息。 在上一個範例中,建立 Azure Cosmos專案時,會執行預先觸發,而要求訊息內文包含要以 JSON 格式建立的專案。

當觸發程式註冊時,您可以指定它可以執行的操作。 此觸發程式應該使用 的值建立,這表示不允許在取代作業中使用觸發程式,如下列程式碼 TriggerOperationTriggerOperation.Create 所示。

若要瞭解如何註冊和呼叫觸發前觸發機的範例 ,請參閱觸發 前和 觸發 後文章。

觸發後

下列範例顯示觸發後。 這會觸發中繼資料專案的查詢,並更新其新建立專案的詳細資訊。

function updateMetadata() {
var context = getContext();
var container = context.getCollection();
var response = context.getResponse();

// item that was created
var createdItem = response.getBody();

// query for metadata document
var filterQuery = 'SELECT * FROM root r WHERE r.id = "_metadata"';
var accept = container.queryDocuments(container.getSelfLink(), filterQuery,
    updateMetadataCallback);
if(!accept) throw "Unable to update metadata, abort";

function updateMetadataCallback(err, items, responseOptions) {
    if(err) throw new Error("Error" + err.message);
        if(items.length != 1) throw 'Unable to find metadata document';

        var metadataItem = items[0];

        // update metadata
        metadataItem.createdItems += 1;
        metadataItem.createdNames += " " + createdItem.id;
        var accept = container.replaceDocument(metadataItem._self,
            metadataItem, function(err, itemReplaced) {
                    if(err) throw "Unable to update metadata, abort";
            });
        if(!accept) throw "Unable to update metadata, abort";
        return;
}
}

請注意,Azure 和 DB 中觸發程式Cosmos執行。 觸發後會做為基礎專案本身的相同交易之一部分執行。 觸發後執行期間發生例外會失敗整個交易。 任何已提交專案都會退回,並退回例外。

若要瞭解如何註冊和呼叫觸發前觸發機的範例 ,請參閱觸發 前和 觸發 後文章。

如何撰寫使用者定義的函數

下列範例會建立 UDF,以計算各種收入方括弧的營業稅。 這個使用者定義的函數就會在查詢內使用。 為了本範例的目的,假設有一個稱為「收入」的容器,其屬性如下:

{
   "name": "Satya Nadella",
   "country": "USA",
   "income": 70000
}

以下是計算各種收入方括弧之收入稅的函式定義:

function tax(income) {

        if(income == undefined)
            throw 'no input';

        if (income < 1000)
            return income * 0.1;
        else if (income < 10000)
            return income * 0.2;
        else
            return income * 0.4;
    }

若要瞭解如何註冊及使用使用者定義函數的範例,請參閱如何在 Azure 和 DB Cosmos定義函數

測 井

使用儲存程式、觸發程式或使用者定義函數時,您可以啟用腳本記錄來記錄步驟。 當設定為 true 時,會產生用於調試的字串 EnableScriptLogging ,如下列範例所示:

let requestOptions = { enableScriptLogging: true };
const { resource: result, headers: responseHeaders} await container.scripts
      .storedProcedure(Sproc.id)
      .execute(undefined, [], requestOptions);
console.log(responseHeaders[Constants.HttpHeaders.ScriptLogResults]);

下一個步驟

深入瞭解 Azure 資料庫中的儲存程式、觸發程式及使用者定義函數的撰寫Cosmos方法: