1

在我们的 CRM 环境中,用户与可用时间实体具有 1:N 关系,该实体表示他们一周的实际可用时间。我正在 C# 中寻找一种方法来检索本周特定团队的用户的所有可用时间。

我是 CRM 开发的新手,我环顾四周,但似乎有很多方法可以做到这一点,我不确定哪种方法最适合。

语言为 C#,CRM 版本为 MS CRM 4.0

4

1 回答 1

4

我将介绍这 3 位:一般方法、代码本身以及代码上的一些注释(代码被注释以引起对某些事情的注意,但其中一些将在代码之外进行进一步的解释)。

一般的做法

如您所见,有几种方法可以做事,但对于通过 Web 服务与 CRM 交互的外部应用程序,它归结为 3 个主要选项:

  1. 使用通过在 Web 服务调用中添加 Web 引用获得的强类型方法来检索您的自定义实体(我认为您在上一个问题中提到看到方法爆炸......当您有很多自定义实体时,情况会变得更糟)
  2. DynamicEntity在您的网络服务调用中使用
  3. 获取XML

如果您的系统非常简单,您通常可以使用 (1),但我会推荐 (2) 或 (3) 中的任何一个。使用 (2) 意味着您只需要记住少数 Web 服务方法,如果您曾经使用插件或工作流程序集,因为这些概念可以很好地传播,那么它就很好。(3) 如果您了解 FetchXML 并且可以形成适当的查询,那就太好了。

我通常使用 (2) 来处理这些事情,因为它很常见,这是一种很好的中间方法,就像我说的那样,您的代码将相当容易转换为插件或工作流程序集。FetchXML 很好地执行,但我从来不擅长形成查询 - 我稍后会介绍一些技术,但让我们继续 (2)。

此外,如果您使用它,则DynamicEntity不需要刷新您的 Web 引用,因为您使用它的方式及其Property对象数组(基本上您以强类型为代价获得灵活性),正如您将在代码中看到的那样。如果您选择 (1),您将获得针对您的自定义实体的强类型,但您必须根据人们对您的实体所做的更改的节奏不断刷新您的 WebReference。

编码

这是在一个小控制台应用程序中,我在其中向 CRM 服务添加了一个 WebReference,并进行了一些调用来模拟您的场景。代码应该传递到其他应用程序,如网络应用程序。我试图对其发表评论,因此在转到下一部分之前可能值得一读。

注意。我并没有声称这是世界上最好的代码,但它似乎确实有效,应该让你开始)

注意2。我错误地调用了我的命名空间作为网络参考CrmService——请不要和我犯同样的错误......)

static void Main(string[] args)
{
        CrmService.CrmService svc = new CrmService.CrmService();
        svc.CrmAuthenticationTokenValue = GetToken();
        svc.UseDefaultCredentials = true;

        #region 1 - Retrieve users in team
        RetrieveMembersTeamRequest teamMembersReq = new RetrieveMembersTeamRequest()
        {
            EntityId = new Guid("D56E0E83-2198-E211-9900-080027BBBE99"), //You'll need the team GUID
            ReturnDynamicEntities = true
        };

        ColumnSet teamMembersReqColumnSet = new ColumnSet();
        teamMembersReqColumnSet.Attributes = new string[] { "systemuserid", "domainname" };

        teamMembersReq.MemberColumnSet = teamMembersReqColumnSet; //Don't use: teamMembersReq.MemberColumnSet = new AllColumns()

        List<Guid> userIdList = new List<Guid>();
        RetrieveMembersTeamResponse teamMembersResp = svc.Execute(teamMembersReq) as RetrieveMembersTeamResponse;
        if (teamMembersResp != null)
        {
            BusinessEntity[] usersInTeamAsBusinessEntity = teamMembersResp.BusinessEntityCollection.BusinessEntities;
            List<DynamicEntity> usersInTeamAsDynEntity = usersInTeamAsBusinessEntity.Select(be => be as DynamicEntity).ToList(); //BusinessEntity not too useful, cast to DynamicEntity

            foreach (DynamicEntity de in usersInTeamAsDynEntity)
            {
                Property userIdProp = de.Properties.Where(p => p.Name == "systemuserid").FirstOrDefault();
                Property domainNameProp = de.Properties.Where(p => p.Name == "domainname").FirstOrDefault();

                if (userIdProp != null)
                {
                    KeyProperty userIdKeyProp = userIdProp as KeyProperty; //Because it is the unique identifier of the entity
                    userIdList.Add(userIdKeyProp.Value.Value); //Chuck in a list for use later
                    Console.Write("Key: " + userIdKeyProp.Value.Value.ToString());
                }

                if (domainNameProp != null)
                {
                    StringProperty domainNameStringProp = domainNameProp as StringProperty; //Because its data type is varchar
                    Console.Write("| Domain Name: " + domainNameStringProp.Value);
                }

                Console.WriteLine();
            }
        }
        #endregion

        /*
         * For this example I have created a dummy entity called new_availablehours that is in a 1:N relationship with use (i.e. 1 user, many new_available hours). 
         * The test attributes are :
         *      - the relationship attribute is called new_userid...this obviously links across to the GUID from systemuser
         *      - there is an int data type attribute called new_hours
         *      - there is a datetime attribute called new_availabilityday
         */
        #region Retrieve From 1:N
        RetrieveMultipleRequest req = new RetrieveMultipleRequest();
        req.ReturnDynamicEntities = true; //Because we love DynamicEntity

        //QueryExpression says what entity to retrieve from, what columns we want back and what criteria we use for selection
        QueryExpression qe = new QueryExpression();
        qe.EntityName = "new_availablehours"; //the entity on the many side of the 1:N which we want to get data from

        qe.ColumnSet = new AllColumns(); //Don't do this in real life, limit it like we did when retrieving team members

        /*
         * In this case we have 1 x Filter Expression which combines multiple Condition Operators
         * Condition Operators are evaluated together using the FilterExpression object's FilterOperator property (which is either AND or OR)
         * 
         * So if we use AND all conditions need to be true and if we use OR then at least one of the conditions provided needs to be true
         * 
         */
        FilterExpression fe = new FilterExpression();
        fe.FilterOperator = LogicalOperator.And;

        ConditionExpression userCondition = new ConditionExpression();
        userCondition.AttributeName = "new_userid"; //The attribute of qe.EntityName which we want to test against
        userCondition.Operator = ConditionOperator.In; //Because we got a list of users previously, the appropriate check is to get records where new_userid is in the list of valid ones we generated earlier
        userCondition.Values = userIdList.Select(s => s.ToString()).ToArray(); //Flip the GUID's to strings (seems that CRM likes that) then set them as the values we want to evaulate
        //OK - so now we have this userCondition where valid records have their new_userid value in a collection of ID's we specify

        ConditionExpression dateWeekBound = new ConditionExpression();
        dateWeekBound.AttributeName = "new_availabilityday";
        dateWeekBound.Operator = ConditionOperator.ThisWeek; //ConditionOperator has a whole bunch of convenience operators to deal with dates (e.g. this week, last X days etc) - check them out as they are very handy

        /*
         * As an aside, if we didn't want to use the convenience operator (or if none was available) we would have to create a ConditionExpression like:
         * 
         * ConditionExpression dateLowerBound = new ConditionExpression();
         * dateLowerBound.AttributeName = "new_availabilityday";
         * dateLowerBound.Operator = ConditionOperator.OnOrAfter;
         * dateLowerBound.Values = new object[] { <Your DateTime object here> };
         * 
         * And a corresponding one for the upper bound using ConditionOperator.OnOrBefore
         * 
         * Another alternative is to use ConditionOperator.Between. This is flexible for any sort of data, but the format of the Values array will be something like:
         *      ce.Values = new object[] { <lower bound>, <upper bound> };
         */

        fe.Conditions = new ConditionExpression[] { userCondition, dateWeekBound }; //Add the conditions to the filter
        qe.Criteria = fe; //Tell the query what our filters are
        req.Query = qe; //Tell the request the query we want to use

        RetrieveMultipleResponse resp = svc.Execute(req) as RetrieveMultipleResponse;
        if (resp != null)
        {
            BusinessEntity[] rawResults = resp.BusinessEntityCollection.BusinessEntities;
            List<DynamicEntity> castedResults = rawResults.Select(r => r as DynamicEntity).ToList();

            foreach (DynamicEntity result in castedResults)
            {
                Property user = result.Properties.Where(p => p.Name == "new_userid").FirstOrDefault();
                Property hours = result.Properties.Where(p => p.Name == "new_hours").FirstOrDefault();

                if (user != null)
                {
                    LookupProperty relationshipProperty = user as LookupProperty; //Important - the relationship attribute casts to a LookupProperty
                    Console.Write(relationshipProperty.Value.Value.ToString() + ", ");
                }

                if (hours != null)
                {
                    CrmNumberProperty hoursAsCrmNumber = hours as CrmNumberProperty; //We also have CrmFloatProperty, CrmDecimalProperty etc if the attribute was of those data types
                    Console.Write(hoursAsCrmNumber.Value.Value);
                }

                Console.WriteLine();
            }
        }
        #endregion

        Console.ReadLine();
    }

    static CrmAuthenticationToken GetToken()
    {
        CrmAuthenticationToken token = new CrmAuthenticationToken();
        token.AuthenticationType = 0; //Active Directory
        token.OrganizationName = "DevCRM";

        return token;
    }

所以..那是什么?

我不打算逐一介绍,而是重点介绍以下要点:

  1. 使用服务时的关键方法是Execute()我们将请求对象传递给它并返回响应对象的方法。请求都将是 class 的对象<Operation>Request,响应将是 class 的对象<Operation>Response
  2. 您通常希望使用DynamicEntity-<Operation>Request对象通常会公开一个名为的属性ReturnDynamicEntities,您应该将其设置为true
  3. 大多数<Operation>Request对象都有一个ColumnSet属性,您可以在其中指定要返回的属性。指定通常是不好的做法AllColumns(),相反,您应该明确说明要返回的数据。属性需要匹配它们在 CRM 中的名称(形式如此<prefix>_<field name>)并且全部小写
  4. 让用户加入团队并不太有趣,因为它是 CRM 中的预定义操作,没有什么特别之处……在这种情况下,SDK是您的朋友,因为它会向您展示这些是如何工作的
  5. 检索一堆自定义实体是一个更有趣的用例,我们通常可以使用RetrieveMultipleRequestandRetrieveMultipleResponse方法来获取它们(如果您只想要一条记录,那么您可以使用RetrieveRequestand RetrieveResponse...但是您需要知道您的 GUID寻找馈入RetreiveRequest对象)。
  6. 因为RetrieveMultipleRequest我们向它提供了一个查询(QueryExpression),它说明了EntityName我们想要获得多个实体()ColumnSet,我们想要返回的那个实体的属性()以及Criteria用于选择我们想要的实际记录的过滤器()
  7. 专注于QueryExpression,FilterExpression和的使用ConditionExpression。重要的是要知道您可以使用哪些运算符ConditionExpression- 我尝试在代码中调用一些运算符,但 SDK 再次成为您了解可用运算符的最佳朋友
  8. 我没有介绍的是更复杂的过滤,例如 (x OR y) AND z。这里有一个相当好的例子。这只是一种不同的使用方式FilterExpressionConditionExpression
  9. 请注意,它RetrieveMultipleResponse包含一个数组BusinessEntity。它BusinessEntity本身没什么用,所以我们把它放到一个列表中DynamicEntity- LINQ 在这里真的是你的朋友,而且对于 CRM 的东西有点混乱,LINQ 派上用场了
  10. 注意我们如何检查属性 -de.Properties.Where(p => p.Name == "systemuserid").FirstOrDefault();然后检查它是否是NULL. 这是因为如果在 CRM 中记录的属性NULL不会从服务调用中返回 - 所以仅仅因为您在请求中请求属性ColumnSet不要自动假设它存在(除非您将其设置为强制在 CRM 中 - 然后可能没问题)...测试它,您的应用程序将变得不那么脆弱。
  11. 类本身的Property价值有限——要真正使用属性,您必须将其转换为实际存在的东西。我一直在喋喋不休,但 SDK 会告诉你类型是什么,但一段时间后它开始感觉自然,例如记录的 GUID 在 a 中KeyProperty,ints 在 in CrmNumberProperty,floats 在 in CrmFloatProperty,stringsStringProperty等。注意弱类型(我之前提到过),我们必须按名称获取属性,转换为正确的值等

其他要点

  1. 通常情况下,您必须对服务调用非常健谈——据我所知,在针对 CRM 进行开发时,这很正常(不过我只能谈谈我自己的经验)
  2. 在你的编码方式中真正防御是很重要的——检查属性是否存在,检查你正在转换为正确的类型等。
  3. 如果您必须捕获异常,它将是 aSoapException并且您通常想要的信息将在Detail属性中 - 记住这一点非常重要,否则您会查看异常并认为它并没有告诉您一大堆
  4. 咨询 CRM 中的自定义项,以确定关系属性、数据类型等的名称。我喜欢在进行开发时为我需要的实体打开自定义窗口,以便于参考。
  5. FetchXML非常强大但非常繁琐。如果你擅长它,那么你会获得很多好的里程 - 像这样的工具很有用。另外,一个方便的技巧 - 如果您可以通过 CRM UI 构建您想要的(或您想要的示例)作为高级查找,那么您可以使用此技巧来获取它使用的 FetchXML....您可能会需要调整 GUID 等,但如果您想在代码中使用 FetchXML,它会为您提供一个构建块,因为大多数查询都是为您编写的。
  6. 根据您的部署环境,您可能不得不弄乱使用的凭据,是否通过代理等......典型的网络参考资料。只是值得注意的是,CRM 也不能幸免于这种事情——我在这里没有任何真正的建议,只是一个说明,因为它在过去给我带来了一些“乐趣”
于 2013-03-29T05:45:53.820 回答