如何查询所有现有字段的元数据

15
我们希望让客户端可以发布到像这样的终端点:

We want to enable the client to post to an endpoint such as:

    [Route("Account", Name = "CreateAccount", Order = 1)]
    [HttpPost]
    public Account CreateAccount([FromBody] Account account)
    {
        var newAccount = _accountService.CreateAccountEntity(account);
        return newAccount;
    }

我们知道这可以完成:

Translated:

我们知道这可以完成:

POST [Organization URI]/api/data/v8.2/accounts HTTP/1.1
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

{
    "name": "Sample Account",
    "creditonhold": false,
    "address1_latitude": 47.639583,
    "description": "This is the description of the sample account",
    "revenue": 5000000,
    "accountcategorycode": 1
}
我们如何向消费者展示每个 post/put 的要求?换句话说,如果我需要使用 CRM 2016 提供的 Web API 更新自定义或基本实体上的记录,我如何知道创建或更新实体所需的字段是哪些?编辑:我已尝试了汉克的方法,但它未返回实体的任何元数据:enter image description here

你失败了,因为你使用了错误的实体元数据过滤器。请查看我的答案以获得澄清。 - Pawel Gradecki
3个回答

10
您可以使用WebApi端点查询Dynamics 365元数据,如SDK中所示。
例如,要检索“账户”实体的所有属性(包括要求级别),请执行以下操作:
GET [Organization URI]/api/data/v8.2/EntityDefinitions(LogicalName='account')/Attributes HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
Content-Type: application/json; charset=utf-8

非常感谢。那么如果我想使用 Web API 来创建或更新数据,您能给个例子吗?我需要先查询元数据,然后了解所需内容吗?带有解释的示例会很棒。 - Alex Gordon
@MeggieLuski - 请将此作为新问题发布!如果它回答了您发布的问题,请记得将此答案标记为正确。 - Nicknow

8

如果需要通过SOAP端点获取实体的所有元数据,可以使用RetrieveEntityRequest:

 var request = new RetrieveEntityRequest
 {
       EntityFilters = Microsoft.Xrm.Sdk.Metadata.EntityFilters.All,
       LogicalName = "account"
 }

 var response = (RetrieveEntityResponse)organizationService.Execute(request); 

EntityFilters 是一个枚举,它允许您指定要获取的元数据内容:

[Flags]
public enum EntityFilters
{
    //
    // Summary:
    //     Use this to retrieve only entity information. Equivalent to EntityFilters.Default.
    //     Value = 1.
    Entity = 1,
    //
    // Summary:
    //     Use this to retrieve only entity information. Equivalent to EntityFilters.Entity.
    //     Value = 1.
    Default = 1,
    //
    // Summary:
    //     Use this to retrieve entity information plus attributes for the entity. Value
    //     = 2.
    Attributes = 2,
    //
    // Summary:
    //     Use this to retrieve entity information plus privileges for the entity. Value
    //     = 4.
    Privileges = 4,
    //
    // Summary:
    //     Use this to retrieve entity information plus entity relationships for the entity.
    //     Value = 8.
    Relationships = 8,
    //
    // Summary:
    //     Use this to retrieve all data for an entity. Value = 15.
    All = 15
}

这是一个标志枚举,您可以像这样使用它:
var request = new RetrieveEntityRequest
{
     EntityFilters = EntityFilters.Privileges | EntityFilters.Entity,
     LogicalName = "account"
} 

或者简单地使用All值来获取所有必要的元数据。在您的尝试中,您未能检索到元数据,因为您仅请求实体元数据,而您对属性元数据感兴趣。
因此,以您的代码片段为基础,我会按照以下方式使用它:
[Route("Account", Name = "CreateAccount", Order = 1)]
[HttpPost]
public Account CreateAccount([FromBody] Account account)
{
    VerifyRequiredFields(account);
    var newAccount = _accountService.CreateAccountEntity(account);
    return newAccount;
}

private void VerifyRequiredFields(Account account)
{
     var response = GetEntityMetadata(account);
     var requiredAttributes = response.EntityMetadata.Attributes.Where(a => a.RequiredLevel?.Value == AttributeRequiredLevel.SystemRequired);
     foreach(var requiredAttribute in requiredAttributes)
     {
          if(CheckIfValueIsProvided(requiredAttribute.LogicalName, account))
          {
               throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, $"You are missing required value {requiredAttribute.LogicalName}"));
          }
     }
}

方法GetEntityMetadata只是在执行前一个示例中的操作,因此调用RetrieveEntityRequest并返回RetrieveEntityResponse
当然,方法CheckIfValueIsProvided的实现取决于您的帐户模型类如何定义,但您可能需要在您的模型和CRM实体模型之间建立某种映射(以知道如何将例如字段“accountnumber”映射到您的模型中的某个字段)。这远远超出了本问题的范围,但我相信您已经知道足够启动。
请记住,这只是一个示例。您不应该将此逻辑保留在控制器类中,而应将其移至某个实用程序类中,以便在不同的控制器中重复使用。元数据很少更改(您可能对此更改有控制),因此您也可能希望在Web应用程序中缓存元数据等。我希望您已经有了一个想法,但整个逻辑设计另外一回事。
如果您想从JavaScript中执行此操作,您可能应该坚持使用webAPI:
http://CRMADDRESS/api/data/v8.2/EntityDefinitions(LogicalName='account')/Attributes?$select=LogicalName,RequiredLevel

这将为您获取所需的内容(属性名称及其所需级别)。它看起来会像这样:

{
  "LogicalName":"preferredcontactmethodcodename","RequiredLevel":{
    "Value":"None","CanBeChanged":false,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
  },"MetadataId":"8663b910-af86-4dea-826e-8222706372f4"
},{
  "@odata.type":"#Microsoft.Dynamics.CRM.StringAttributeMetadata","LogicalName":"emailaddress3","RequiredLevel":{
    "Value":"None","CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
  },"MetadataId":"97fb4aae-ea5d-427f-9b2b-9a6b9754286e"
},{
  "@odata.type":"#Microsoft.Dynamics.CRM.StringAttributeMetadata","LogicalName":"emailaddress2","RequiredLevel":{
    "Value":"None","CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
  },"MetadataId":"98b09426-95ab-4f21-87a0-f6775f2b4210"
},{
  "@odata.type":"#Microsoft.Dynamics.CRM.StringAttributeMetadata","LogicalName":"emailaddress1","RequiredLevel":{
    "Value":"None","CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
  },"MetadataId":"b254ab69-de5a-4edb-8059-bdeb6863c544"
},{
  "@odata.type":"#Microsoft.Dynamics.CRM.StringAttributeMetadata","LogicalName":"masteraccountidyominame","RequiredLevel":{
    "Value":"None","CanBeChanged":false,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
  },"MetadataId":"a15dedfc-9382-43ac-8d10-7773aa3eefeb"
},{
  "@odata.type":"#Microsoft.Dynamics.CRM.StringAttributeMetadata","LogicalName":"address1_city","RequiredLevel":{
    "Value":"None","CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
  },"MetadataId":"ca8d0a94-8569-4154-b511-718e11635449"
},{
  "@odata.type":"#Microsoft.Dynamics.CRM.LookupAttributeMetadata","LogicalName":"slaid","RequiredLevel":{
    "Value":"None","CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
  },"MetadataId":"6bdcd7f1-5865-4fef-91b0-676824b18641"
}

你可以使用这个来在客户端验证请求,给用户一个提示,在他向服务器发送请求之前,提醒他缺少重要的数据。

还有一件事,因为我不确定你是否意识到 - 从CRM元数据中获取的是字段的字符串名称(例如“accountnumber”),因此您必须能够以某种方式将此字符串“accountnumber”映射到您的模型属性(以检查用户是否提供了其值)。我希望现在这一点已经清楚了。 - Pawel Gradecki
非常感谢您的澄清和见解!我仍然不确定如何针对非静态模式创建映射。 - Alex Gordon
谢谢你再次进行讨论。我假设我的问题源自无知,但是为什么客户端不能在进行任何其他类型的POST / GET / PATCH请求之前查询元数据,然后拥有元数据,现在他们能够创建有效的请求呢? - Alex Gordon
1
您可以使用元数据构建有效的请求。但是您的问题(我仍然在提到它,因为我不想离题)是您想要检查是否填写了必填值。如果您知道从元数据中需要“accountnumber”,则必须以某种方式将此“accountnumber”映射到您使用的模型(也许这将是一个名为“accountnumber”的字段,但也可能是“CrmAccountNumber”等字段,我不知道您的模型),并检查该字段是否具有值。希望现在对您清楚了。在Stack Overflow上禁止讨论,所以我希望如此 :) - Pawel Gradecki
1
你目前的问题实际上与最初的问题相去甚远 - 你想获取哪些字段是必需的信息,而这些信息存储在元数据中。现在你应该尝试对此进行一些操作,如果遇到问题,可以在 Stack Overflow 上创建一个新的问题,但最初的问题已经解决了,所以让我们不要在这里继续讨论了。 - Pawel Gradecki
显示剩余6条评论

5
您可以使用RetrieveEntityRequest来获取实体的元数据。
以下示例检索了实体“账户”的元数据:
var request = new RetrieveEntityRequest
{
    EntityFilters = EntityFilters.Entity | EntityFilters.Attributes,
    LogicalName = "account"
};

var response = (RetrieveEntityResponse)_serviceProxy.Execute(request);

响应对象包含一个 EntityMetadata 属性。在其中,您可以像这样找到属性的要求设置:
EntityMetadata metadata = reponse.EntityMetadata;
bool isRevenueRequired = metadata.Attributes
    .First<AttributeMetadata>(a -> a.LogicalName == "revenue")
    .RequiredLevel.Value == AttributeRequiredLevel.ApplicationRequired;

1
这并没有回答如何使用WebApi端点检索元数据。 - Nicknow
1
@Nicknow 看了一下问题的编辑历史,加入的 C# 代码片段和标签,我觉得这个问题有点含糊不清。也许在这里进行编辑比较合适? - Henk van Boeijen
@HenkvanBoeijen,我尝试了你提出的方法,请查看我的修改后的问题。 - Alex Gordon
@MeggieLuski 抱歉,EntityFilters属性是一个标志枚举。当需要属性的元数据时,必须设置Attributes标志。在我的示例中,Attributes集合为null。我修改了我的答案。 - Henk van Boeijen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接