在CRM 4 C中检索自定义实体#

时间:2013-03-22 16:37:24

标签: c# dynamics-crm-4 entities

在我们的CRM环境中,用户与可用小时实体之间存在1:N关系,表示他们实际可用的工作小时数。我正在寻找c#中的一种方法来检索当前特定团队用户的所有可用时间。

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

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

1 个答案:

答案 0 :(得分:4)

我将覆盖这是3位:一般方法,代码本身,然后是代码的一些注释(代码注释以引起对某些事情的注意,但是其中一些将在代码之外进行进一步解释)。

一般方法

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

  1. 使用您在Web服务调用中添加Web引用时获得的强类型方法来检索您的自定义实体(我想您在上一个问题中提到过看到方法爆炸......当您有很多方法时,这会变得更糟自定义实体)
  2. 在网络服务电话中使用DynamicEntity
  3. FetchXML
  4. 如果您的系统非常简单,您通常可以逃避(1),但我会推荐(2)或(3)中的任何一个。使用(2)意味着你只需要记住一些Web服务方法及其好处,如果你进入插件或工作流程序集,因为概念相当好。 (3)如果您了解FetchXML并且可以形成适当的查询,那就很好。

    我通常使用(2)来处理这些事情,因为它是常见的,它是一个很好的中途方法,就像我说的,你的代码很容易翻译成插件或者工作流程组装FetchXML运行良好,但我从来不擅长形成查询 - 我稍后将介绍一些技巧,但请放弃(2)。

    此外,如果您使用DynamicEntity,则不应该刷新Web引用,因为您使用它的方式及其Property对象数组(基本上您可以获得灵活性正如您在代码中看到的那样强烈打字。如果您使用(1),您可以对自定义实体进行强大的输入,但是您必须根据人们对您的实体所做的更改的节奏来不断刷新您的WebReference。

    守则

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

    NB。我不会声称这是世界上最好的代码,但它确实有用,应该让你入门)

    NB2。我错误地将我的命名空间用于网络参考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()方法,我们向它传递一个请求对象并返回一个响应对象。这些请求都是类<Operation>Request的对象,响应将是类<Operation>Response的对象。
    2. 您通常希望使用DynamicEntity - <Operation>Request个对象通常会公开名为ReturnDynamicEntities的属性,您应将其设置为true
    3. 大多数<Operation>Request个对象都有ColumnSet属性,您可以在其中指定要返回的属性。指定AllColumns()通常是不好的做法,而应该明确指出要返回的数据。属性需要与CRM中的名称匹配(形式为<prefix>_<field name>),全部为小写
    4. 让用户加入团队并不是一件有趣的事情,因为它在CRM中是预定义的操作而没有什么特别的......在这种情况下SDK是你的朋友,因为它会显示你是如何工作的
    5. 检索一组自定义实体是一个更有趣的用例,我们通常可以使用RetrieveMultipleRequestRetrieveMultipleResponse方法解决这些问题(如果您只需要一条记录,那么您可以使用{{ 1}}和RetrieveRequest ...但是你需要知道你想要提供给RetrieveResponse对象的GUID。
    6. 对于RetreiveRequest,我们向其提供一个查询(RetrieveMultipleRequest),该查询说明了我们想要获得多个实体(QueryExpression)的多个属性(EntityName)我们想要返回的实体和用于选择我们想要的实际记录的过滤器(ColumnSet
    7. 关注CriteriaQueryExpressionFilterExpression的使用情况。需要知道的一件重要事项是ConditionExpression中您可以使用哪些运算符 - 我试图在代码中调用一些,但SDK再一次是您最好的朋友,知道可用的内容
    8. 我还没有涵盖的东西是更复杂的过滤,如(x OR y)和z。有一个相当不错的例子here。这只是使用ConditionExpressionFilterExpression
    9. 的另一种方式
    10. 请注意,ConditionExpression包含RetrieveMultipleResponse数组。 BusinessEntity本身是没用的,所以我们将它转​​换为BusinessEntity列表 - LINQ真的是你的朋友,而且有很多混乱CRM的东西,LINQ派上用场
    11. 请注意我们如何检查属性 - DynamicEntity,然后检查它是否为de.Properties.Where(p => p.Name == "systemuserid").FirstOrDefault();。这是因为如果在CRM中,记录的属性为NULL,则无法从服务调用中返回 - 因此您只需要在NULL中请求属性即可自动假设它在那里(除非你在CRM中将其设置为强制性 - 然后可能正常)...测试它,你的应用程序将变得不那么脆弱。
    12. ColumnSet类本身的价值有限 - 要真正使用属性,你必须把它投射到它实际上的东西。我一直在努力,但SDK会告诉你这些类型是什么,但一段时间后它开始变得自然,例如记录的GUID位于Property,整数位于KeyProperty,浮点数位于CrmNumberProperty,字符串位于CrmFloatProperty等。请注意弱类型(我之前提到过) )我们必须按名称获取属性,投射到适当的值等
    13. 其他要点

      1. 通常情况下,您必须对服务电话非常健谈 - 从我所看到的情况来看,这对于CRM开发时是相当正常的(我只能谈谈自己的经历)
      2. 在编码方式上保持防御非常重要 - 检查属性是否存在,检查您是否正在转换为正确的类型等。
      3. 如果你必须发现异常,它将成为SoapException并且您通常想要的信息将出现在Detail属性中 - 非常重要的是要记住这个或者你看一下例外,并认为它没有告诉你一大堆
      4. 请回顾CRM中的自定义,找出关系属性,数据类型等的名称。我喜欢在开发时需要打开实体的自定义窗口,以便于参考。
      5. StringProperty真的很强大,但非常繁琐。如果你擅长它,那么你会获得很多好的里程 - 像this这样的工具很有用。另外,一个方便的技巧 - 如果你可以构建你想要的东西(或你想要的一个例子)作为通过CRM UI的高级查找,那么你可以使用这个trick来获得它使用的FetchXML ....您可能需要调整GUID等,但如果您想在代码中使用FetchXML,它会为您提供构建块,因为大部分查询都是为您编写的。
      6. 根据您的部署环境,您可能不得不弄乱使用的凭据,是否通过代理等...典型的Web引用内容。值得注意的是,CRM并不是免于这种事情 - 我在这里没有任何真正的建议,只是一个注意事项,因为它给我带来了一些“有趣”的信息。在过去