You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过C#外部程序执行Salesforce Marketing Cloud的SQL查询

问题描述

我希望自动化提取Salesforce Marketing Cloud(原Exact Target)中部分Data Extension的数据。目前我在Query Studio中运行如下SQL查询示例:

select distinct CompositeKey as ActivityCompositeKey, Type, BounceCategory from SendLogAnalyticsForExport where Type='EmailSent'

请问如何通过我将要编写的外部程序(优先使用C#)实现这一操作?

我拥有连接Marketing Cloud API的凭证,以及认证、REST和SOAP的URL:

AuthURL: https://XXXXXXXXXXX.auth.marketingcloudapis.com/v2/token
ClientId: YYYYYYYYYYYYYYYY
ClientSecret: ZZZZZZZZZZZZZ
SoapURL: https://XXXXXXXXXXX.soap.marketingcloudapis.com
RestURL: https://XXXXXXXXXXX.rest.marketingcloudapis.com

我查阅了「Marketing Cloud Engagement APIs and SDKs」文档,了解到REST API和SOAP API,但未找到针对上述SQL格式查询(即Query Studio中运行的类SQL查询)的相关内容。我找到最接近的是Salesforce「Data Cloud Reference Guide」中的「Query Data using Query API V1」文档,但它似乎并非针对Marketing Cloud,且说明十分简略:未提及实际调用的端点,也没有认证方法的相关信息。此外,我还查看了FuelSDK-CSharp库,但同样未找到针对Data Extension执行类SQL查询的相关内容。

解决方案

在Marketing Cloud中,要执行类SQL查询(和Query Studio逻辑一致),需要通过Query Definition API创建、运行查询,再从生成的目标Data Extension中提取结果。以下是两种C#实现方式:

方式一:直接调用REST API实现

步骤1:获取API访问令牌

通过OAuth2认证获取访问令牌:

using System.Net.Http;
using System.Text;
using System.Text.Json;

public async Task<string> GetAuthToken()
{
    var authUrl = "https://XXXXXXXXXXX.auth.marketingcloudapis.com/v2/token";
    var clientId = "YYYYYYYYYYYYYYYY";
    var clientSecret = "ZZZZZZZZZZZZZ";

    var requestBody = new
    {
        client_id = clientId,
        client_secret = clientSecret,
        grant_type = "client_credentials"
    };

    using var client = new HttpClient();
    var content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json");
    var response = await client.PostAsync(authUrl, content);
    response.EnsureSuccessStatusCode();

    var responseJson = await response.Content.ReadAsStringAsync();
    var tokenData = JsonSerializer.Deserialize<JsonElement>(responseJson);
    return tokenData.GetProperty("access_token").GetString();
}

步骤2:创建Query Definition

提交SQL查询,指定用于存储结果的目标Data Extension:

public async Task<string> CreateQueryDefinition(string accessToken)
{
    var restUrl = "https://XXXXXXXXXXX.rest.marketingcloudapis.com/hub/v1/queryDefinitions";
    var querySql = "select distinct CompositeKey as ActivityCompositeKey, Type, BounceCategory from SendLogAnalyticsForExport where Type='EmailSent'";

    var queryDefinition = new
    {
        name = "Extract_SendLog_EmailSent", // 自定义查询名称
        description = "Extract EmailSent records from SendLogAnalyticsForExport",
        targetKey = "Target_DE_ExternalKey", // 目标DE的外部键
        queryText = querySql,
        categoryId = 12345, // 存储查询的文件夹ID,可在MC界面查询
        targetType = "DE"
    };

    using var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
    var content = new StringContent(JsonSerializer.Serialize(queryDefinition), Encoding.UTF8, "application/json");
    var response = await client.PostAsync(restUrl, content);
    response.EnsureSuccessStatusCode();

    var responseJson = await response.Content.ReadAsStringAsync();
    var resultData = JsonSerializer.Deserialize<JsonElement>(responseJson);
    return resultData.GetProperty("id").GetString();
}

步骤3:运行Query Definition

触发查询执行:

public async Task RunQueryDefinition(string accessToken, string queryDefinitionId)
{
    var restUrl = $"https://XXXXXXXXXXX.rest.marketingcloudapis.com/hub/v1/queryDefinitions/{queryDefinitionId}/actions/start";

    using var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
    var response = await client.PostAsync(restUrl, null);
    response.EnsureSuccessStatusCode();
}

步骤4:从目标Data Extension提取结果

查询执行完成后,调用Data Extension API获取结果:

public async Task<string> GetDEData(string accessToken, string deExternalKey)
{
    var restUrl = $"https://XXXXXXXXXXX.rest.marketingcloudapis.com/data/v1/customobjectdata/key/{deExternalKey}/rowset?$pageSize=5000";

    using var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
    var response = await client.GetAsync(restUrl);
    response.EnsureSuccessStatusCode();

    return await response.Content.ReadAsStringAsync();
}

方式二:使用FuelSDK-CSharp实现

FuelSDK-CSharp封装了SOAP API,可通过ETQueryDefinition类实现查询创建与执行:

using FuelSDK;
using System.Linq;

public void ExecuteQueryWithFuelSDK()
{
    var etClient = new ETClient
    {
        AuthStub = new ETSoapClient
        {
            Url = "https://XXXXXXXXXXX.soap.marketingcloudapis.com/Service.asmx",
            ClientId = "YYYYYYYYYYYYYYYY",
            ClientSecret = "ZZZZZZZZZZZZZ"
        }
    };

    // 创建Query Definition
    var queryDef = new ETQueryDefinition
    {
        Name = "Extract_SendLog_EmailSent",
        QueryText = "select distinct CompositeKey as ActivityCompositeKey, Type, BounceCategory from SendLogAnalyticsForExport where Type='EmailSent'",
        TargetType = "DE",
        TargetKey = "Target_DE_ExternalKey",
        CategoryID = 12345
    };

    var createResponse = queryDef.Post(etClient);
    if (!createResponse.Status)
    {
        throw new Exception($"创建查询失败:{string.Join(", ", createResponse.Results.Select(r => r.ErrorMessage))}");
    }

    // 运行查询
    var queryId = createResponse.Results.First().ObjectID;
    var runResponse = queryDef.Perform(etClient, "Start", new[] { queryId });
    if (!runResponse.Status)
    {
        throw new Exception($"运行查询失败:{string.Join(", ", runResponse.Results.Select(r => r.ErrorMessage))}");
    }

    // 轮询查询状态,确认执行完成(示例逻辑)
    bool isComplete = false;
    while (!isComplete)
    {
        var statusResponse = queryDef.Get(etClient, new[] { queryId });
        if (statusResponse.Status)
        {
            var status = statusResponse.Results.First().Status;
            isComplete = status == "Completed";
            if (!isComplete) System.Threading.Thread.Sleep(5000);
        }
        else
        {
            throw new Exception($"查询状态获取失败:{string.Join(", ", statusResponse.Results.Select(r => r.ErrorMessage))}");
        }
    }

    // 提取目标DE数据
    var de = new ETDataExtension
    {
        ExternalKey = "Target_DE_ExternalKey"
    };
    var retrieveResponse = de.Get(etClient);
    if (retrieveResponse.Status)
    {
        foreach (var result in retrieveResponse.Results)
        {
            // 读取字段值示例:var key = result.Properties["ActivityCompositeKey"]
        }
    }
}

关键说明

  • 目标Data Extension需提前创建,字段结构要与SQL查询的输出完全匹配
  • 查询执行是异步操作,需通过轮询Query Definition的状态确认执行完成(REST API可调用/hub/v1/queryDefinitions/{id}接口查询状态)
  • 若只需简单过滤(无复杂SQL逻辑如distinct、多表关联),可直接调用Data Extension的Retrieve接口并指定过滤条件,无需创建Query Definition

内容的提问来源于stack exchange,提问作者Alterino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 00:43:18