简体   繁体   English

使用实体元数据早期绑定调用 Dynamics Web API

[英]Calling Dynamics Web API with Entity metadata early binding

I would like to consume my organizations dynamics oData endpoint but with early bound classes.我想使用我的组织动态 oData 端点,但使用早期绑定类。 However, there are a lot of early bound tools out there and I wanted to know which one provides the best developer experience/least resistance?但是,有很多早期绑定工具,我想知道哪一个提供了最好的开发人员体验/最少的阻力?

For example, there is this one:例如,有这样一个:

https://github.com/daryllabar/DLaB.Xrm.XrmToolBoxTools https://github.com/yagasoft/DynamicsCrm-CodeGenerator https://github.com/daryllabar/DLaB.Xrm.XrmToolBoxTools https://github.com/yagasoft/DynamicsCrm-CodeGenerator

and so on.等等。 Is there a developer preference/method out there?是否有开发人员偏好/方法?

Early bound classes are for use with the Organization Service which is a SOAP service.早期绑定类用于组织服务,它是一种 SOAP 服务。 The normal way to generate those classes is using CrmSvcUtil .生成这些类的正常方法是使用CrmSvcUtil

OData can be used in Organization Data Service or Web API, but those don't have Early Bound classes. OData 可用于组织数据服务或 Web API,但它们没有 Early Bound 类。

Further reading: Introducing the Microsoft Dynamics 365 web services进一步阅读: 介绍 Microsoft Dynamics 365 Web 服务

It's not impossible to use with standard SOAP Early bound class.与标准 SOAP 早期绑定类一起使用并非不可能。 We just have to be creative.我们只需要有创意。 If we work just with basic attributes (fields, not relationships, ecc) it seems possible.如果我们只使用基本属性(字段,而不是关系,ecc),这似乎是可能的。 For example.例如。 for create and update, OData will not accept the entire early bounded class, just pass the attibutes:对于创建和更新,OData 不会接受整个早期有界类,只需传递属性:

    class Program
{
    static void Main(string[] args)
    {
        string token = System.Threading.Tasks.Task.Run(() => GetToken()).Result;
        CRMWebAPI dynamicsWebAPI = new CRMWebAPI("https:/ORG.api.crm4.dynamics.com/api/data/v9.1/",
                token);


        CRMGetListOptions listOptions = new CRMGetListOptions
        {
            Select = new string[] { "EntitySetName" },
            Filter = "LogicalName eq 'contact'"
        };

        dynamic entityDefinitions = dynamicsWebAPI.GetList<ExpandoObject>("EntityDefinitions", listOptions).Result;

        Contact contact = new Contact
        {
            FirstName = "Felipe",
            LastName = "Test",
            MobilePhone = "38421254"
        };

        dynamic ret = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.Create(entityDefinitions.List[0].EntitySetName, KeyPairValueToObject(contact.Attributes))).Result;



}

    public static async Task<string> GetToken()
    {
        string api = "https://ORG.api.crm4.dynamics.com/";

        ClientCredential credential = new ClientCredential("CLIENT_ID", "CLIENT_SECRET");

        AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/commom/oauth2/authorize");
        return authenticationContext.AcquireTokenAsync(api, credential).Result.AccessToken;

    }

    public static object KeyPairValueToObject(AttributeCollection keyValuePairs)
    {
        dynamic expando = new ExpandoObject();
        var obj = expando as IDictionary<string, object>;
        foreach (var keyValuePair in keyValuePairs)
            obj.Add(keyValuePair.Key,  keyValuePair.Value);
        return obj;

    }
}

It's a simple approach and I didn't went further.这是一个简单的方法,我没有更进一步。 Maybe we have to serealize other objects as OptionSets, DateTime (pass just the string) and EntityReferences but this simple test worked fine to me.也许我们必须将其他对象序列化为 OptionSets、DateTime(只传递字符串)和 EntityReferences,但这个简单的测试对我来说效果很好。 I'm using Xrm.Tools.WebAPI and Microsoft.IdentityModel.Clients.ActiveDirectory.我正在使用 Xrm.Tools.WebAPI 和 Microsoft.IdentityModel.Clients.ActiveDirectory。 Maybe it's a way.也许这是一种方式。

[Edit] [编辑]

And so I decided to go and created a not well tested method to cast the attributes.所以我决定去创建一个没有经过很好测试的方法来转换属性。 Problems: We have to follow OData statments to use the API.问题:我们必须遵循 OData 语句才能使用 API。 To update/create an entity reference we can use this to reference https://www.inogic.com/blog/2016/02/set-values-of-all-data-types-using-web-api-in-dynamics-crm/ So要更新/创建实体引用,我们可以使用它来引用https://www.inogic.com/blog/2016/02/set-values-of-all-data-types-using-web-api-in-dynamics -crm/所以

//To EntityReference //到实体引用

entityToUpdateOrCreate["FIELD_SCHEMA_NAME@odata.bind"] = "/ENTITY_SET_NAME(GUID)";

So, it's the Schema name, not field name.所以,它是架构名称,而不是字段名称。 If you use CamelCase when set you fields name you'll have a problem where.如果您在设置字段名称时使用 CamelCase,您将在何处遇到问题。 We can resolve that with a (to that cute) code我们可以用(那个可爱的)代码解决这个问题

        public static object EntityToObject<T>(T entity) where T : Entity
    {
        dynamic expando = new ExpandoObject();
        var obj = expando as IDictionary<string, object>;
        foreach (var keyValuePair in entity.Attributes)
        {
            obj.Add(GetFieldName(entity, keyValuePair), CastEntityAttibutesValueOnDynamicObject(keyValuePair.Value));
        }
        return obj;

    }

    public static object CastEntityAttibutesValueOnDynamicObject(object attributeValue)
    {
        if (attributeValue.GetType().Name == "EntityReference")
         {
            CRMGetListOptions listOptions = new CRMGetListOptions
            {
                Select = new string[] { "EntitySetName" },
                Filter = $"LogicalName eq '{((EntityReference)attributeValue).LogicalName}'"
            };

            dynamic entitySetName = dynamicsWebAPI.GetList<ExpandoObject>("EntityDefinitions", listOptions).Result.List[0];

            return $"/{entitySetName.EntitySetName}({((EntityReference)attributeValue).Id})";
        }
        else if (attributeValue.GetType().Name == "OptionSetValue")
        {
            return ((OptionSetValue)attributeValue).Value;
        }
        else if (attributeValue.GetType().Name == "DateTime")
        {
            return ((DateTime)attributeValue).ToString("yyyy-MM-dd");
        }
        else if (attributeValue.GetType().Name == "Money")
        {
            return ((Money)attributeValue).Value;
        }
        else if (attributeValue.GetType().Name == "AliasedValue")
        {
            return CastEntityAttibutesValueOnDynamicObject(((AliasedValue)attributeValue).Value);
        }
        else
        {
            return attributeValue;
        }
    }

    public static string GetFieldName<T>(T entity, KeyValuePair<string, object> keyValuePair) where T : Entity
    {
        switch (keyValuePair.Value.GetType().Name)
        {
            case "EntityReference":
                var entityNameList = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.GetEntityDisplayNameList()).Result;

                var firstEntity = entityNameList.Where(x => x.LogicalName == entity.LogicalName).FirstOrDefault();

                var attrNameList = System.Threading.Tasks.Task.Run(async () => await dynamicsWebAPI.GetAttributeDisplayNameList(firstEntity.MetadataId)).Result;

                return attrNameList.Where(x => x.LogicalName == keyValuePair.Key).Single().SchemaName + "@odata.bind";
            case "ActivityParty":
                throw new NotImplementedException(); //TODO
            default:
                return keyValuePair.Key;
        }
    }

Please, note that this approach do not seems fast or good in anyway.请注意,无论如何,这种方法似乎并不快或不好。 It's better if you have all this values as static so we can save some fetches最好将所有这些值都设为静态值,这样我们就可以节省一些提取

[Edit 2] [编辑 2]

I just found on XRMToolBox a plugin called "Early bound generator for Web API" and it seems to be the best option .我刚刚在 XRMToolBox 上发现了一个名为“Web API 的早期绑定生成器”的插件,它似乎是最好的选择 Maybe you should give it a try if you're still curious about that.如果您仍然对此感到好奇,也许您应该尝试一下。 I guess its the best approach.我想这是最好的方法。 The final code is this:最后的代码是这样的:

        static void Main(string[] args)
    {
        string token = Task.Run(() => GetToken()).Result;
        dynamicsWebAPI = new CRMWebAPI("https://ORG.api.crm4.dynamics.com/api/data/v9.1/",
                token);

        Contact contact = new Contact
        {
            FirstName = "Felipe",
            LastName = "Test",
            MobilePhone = "38421254",
            new_Salutation = new EntityReference(new_salutation.EntitySetName, new Guid("{BFA27540-7BB9-E611-80EE-FC15B4281C8C}")),
            BirthDate = new DateTime(1993, 04, 14),
        };

        dynamic ret = Task.Run(async () => await dynamicsWebAPI.Create(Contact.EntitySetName, contact.ToExpandoObject())).Result;
        Contact createdContact = dynamicsWebAPI.Get<Contact>(Contact.EntitySetName, ret, new CRMGetListOptions
        {
            Select = new string[] { "*" }
        }).Result;
    }

and you have to change the ToExpandoObject on Entity.cs class (generated by the plugin)并且您必须更改 Entity.cs 类(由插件生成)上的 ToExpandoObject

        public ExpandoObject ToExpandoObject()
    {
        dynamic expando = new ExpandoObject();
        var expandoObject = expando as IDictionary<string, object>;
        foreach (var attributes in Attributes)
        {
            if (attributes.Key == GetIdAttribute())
            {
                continue;
            }

            var value = attributes.Value;
            var key = attributes.Key;

            if (value is EntityReference entityReference)
            {
                value = $"/{entityReference.EntitySetName}({entityReference.EntityId})";
            }
            else
            {
                key = key.ToLower();

                if (value is DateTime dateTimeValue)
                {
                    var propertyForAttribute = GetPublicInstanceProperties().FirstOrDefault(x =>
                        x.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase));

                    if (propertyForAttribute != null)
                    {
                        var onlyDateAttr = propertyForAttribute.GetCustomAttribute<OnlyDateAttribute>();

                        if (onlyDateAttr != null)
                        {
                            value = dateTimeValue.ToString(OnlyDateAttribute.Format);
                        }
                    }
                }
            }

            expandoObject.Add(key, value);
        }

        return (ExpandoObject)expandoObject;
    }

Links: https://github.com/davidyack/Xrm.Tools.CRMWebAPI链接: https : //github.com/davidyack/Xrm.Tools.CRMWebAPI

https://www.xrmtoolbox.com/plugins/crm.webApi.earlyBoundGenerator/ https://www.xrmtoolbox.com/plugins/crm.webApi.earlyBoundGenerator/

We currently use XrmToolkit which has it's own version of early binding called ProxyClasses but will allow you to generate early binding using the CRM Service Utility (CrmSvcUtil).我们目前使用XrmToolkit ,它有自己的早期绑定版本,称为 ProxyClasses,但允许您使用 CRM 服务实用程序 (CrmSvcUtil) 生成早期绑定。 It does a lot more than just early binding which is why we use it on all of our projects but the early binding features alone would have me sold on it.它不仅仅是早期绑定,这就是我们在所有项目中使用它的原因,但仅早期绑定功能就会让我大吃一惊。 in order to regenerate an entity definition all you do is right click the cs file in visual studio and select regenerate and it is done in a few seconds.为了重新生成实体定义,您只需右键单击 Visual Studio 中的 cs 文件并选择重新生成,即可在几秒钟内完成。

For my first 3 years of CRM development I used the XrmToolbox "Early Bound Generator" plugin which is really helpful as well.在我 CRM 开发的前 3 年中,我使用了 XrmToolbox “Early Bound Generator”插件,它也非常有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM