1

私たちのCRM環境では、ユーザーは1週間の実際の利用可能時間を表す利用可能時間エンティティと1:Nの関係にあります。今週の特定のチームのユーザーが利用できるすべての時間を取得する方法をC#で探しています。

私はCRMでの開発に不慣れで、周りを見回しましたが、これを行う方法はたくさんあるようで、どれが最適かわかりません。

言語はC#で、CRMのバージョンはMSCRM4.0です。

4

1 に答える 1

4

これについては3ビットで説明します。一般的なアプローチ、コード自体、そしてコードに関するいくつかのメモです(ただし、特定のことに注意を引くためにコードにコメントが付けられていますが、コードの外でさらに説明するものもあります)。

一般的方法

これまで見てきたように、物事を行うにはいくつかの方法がありますが、Webサービスを介してCRMと対話する外部アプリケーションの場合、3つの主要なオプションになります。

  1. Webサービス呼び出しにWeb参照を追加して取得した、厳密に型指定されたメソッドを使用して、カスタムエンティティを取得します(前の質問でメソッドの爆発的な増加について言及したと思います...カスタムエンティティが多数ある場合、これはさらに悪化します)
  2. DynamicEntityWebサービスの呼び出しで使用する
  3. FetchXML

システムが非常に単純な場合、通常は(1)で解決できますが、(2)または(3)のいずれかをお勧めします。(2)を使用するということは、ほんの一握りのWebサービスメソッドを覚えておけばよいということです。プラグインやワークフローアセンブリに移行した場合は、概念がかなりうまく伝わるので、それは良いことです。(3)FetchXMLを知っていて、適切なクエリを作成できる場合に適しています。

私は通常、(2)を使用してこれらのことにアプローチします。これは、一般的に見られるアプローチの中間であり、前述のように、コードをプラグインまたはワークフローアセンブリに変換するのはかなり簡単です。FetchXMLは非常にうまく機能しますが、クエリの作成が得意ではありませんでした。これについては後でいくつかのテクニックを説明しますが、(2)を使用します。

また、使用する場合は、コードでわかるように、Web参照とそのオブジェクトの配列(基本的には強い型付けを犠牲にして柔軟性が得られます)を操作するDynamicEntity方法のために、Web参照を更新する必要はありません。Property(1)を使用すると、カスタムエンティティに対して強い型付けが発生しますが、エンティティに加えられる変更の頻度に応じて、WebReferenceを更新し続ける必要があります。

コード

これは、CRMサービスにWebReferenceを追加し、シナリオをシミュレートするためにいくつかの呼び出しを行った小さなコンソールアプリケーションにあります。コードは、Webアプリなどの他のアプリにも引き継がれる必要があります。コメントしようとしたので、次のセクションに進む前に一読する価値があります。

:これが世界最高のコードであるとは言いませんが、機能しているようで、開始する必要があります)

注2。Web参照用に名前空間を呼び出すのを間違えました。私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と呼ばれるプロパティを公開しますReturnDynamicEntitiestrue
  3. ほとんどの<Operation>Requestオブジェクトには、ColumnSet返す属性を指定できるプロパティがあります。通常、指定するAllColumns()ことはお勧めできません。代わりに、返すデータを明示する必要があります。属性は、CRM(形式のように<prefix>_<field name>)での名前とすべて小文字で一致する必要があります
  4. チームにユーザーを参加させることは、CRMで事前定義された操作であり、特別なことではないため、それほど面白くありません...この場合、SDKは、これらがどのように機能するかを示してくれるので、友だちです。
  5. 多数のカスタムエンティティを取得することは、より興味深いユースケースであり、通常、RetrieveMultipleRequestandRetrieveMultipleResponseメソッドを使用してこれらを取得できます(1つのレコードのみが必要な場合は、使用するだけRetrieveRequestRetrieveResponse...ただし、自分が何であるかについてのGUIDを知っている必要がありますオブジェクトにフィードするために探していRetreiveRequestます)。
  6. 何のentity()を取得したいか、返すエンティティのattributes()、実際のレコードを選択するために使用されるfilter()をRetrieveMultipleRequest指定するクエリ()をフィードします。QueryExpressionEntityNameColumnSetCriteria
  7. QueryExpression、、FilterExpressionおよびの使用法に焦点を当てConditionExpressionます。知っておくべき重要なことは、どの演算子を使用できるかということですConditionExpression。コードでいくつかの演算子を呼び出そうとしましたが、SDKは、何が使用できるかを知るための親友です。
  8. 私がカバーしていないものは、(x OR y)ANDzのようなより複雑なフィルタリングです。ここにかなり良い例があります。使い方が違うだけFilterExpressionConditionExpression
  9. RetrieveMultipleResponseの配列が含まれていることに注意してくださいBusinessEntity。それBusinessEntity自体はかなり役に立たないので、それをリストにキャストしDynamicEntityます-LINQは本当にここであなたの友達であり、CRMのものをかなりいじるのにLINQは便利です
  10. プロパティをチェックする方法に注意してください-de.Properties.Where(p => p.Name == "systemuserid").FirstOrDefault();次に、それがであるかどうかをチェックしますNULL。これは、CRMでレコードの属性がNULLサービス呼び出しから返されない場合、その属性を要求したからといってColumnSet、そこにあると自動的に想定しないためです(必須として設定されている場合を除く)。 CRMで-それならおそらくOK)...それをテストすると、アプリの脆弱性が大幅に低下します。
  11. クラス自体のProperty値は限られています。プロパティを実際に操作するには、実際のプロパティにキャストする必要があります。私はそれについてずっと言い続けていますが、SDKはタイプが何であるかを教えてくれますが、しばらくすると自然に感じ始めます。たとえば、レコードのGUIDはKeyProperty、、intはCrmNumberProperty、floatはCrmFloatProperty、stringsStringPropertyなどです。名前でプロパティを取得したり、適切な値にキャストしたりする必要がある弱い型付け(前述)

その他のポイント

  1. 通常、サービスコールについてはかなりおしゃべりする必要があります。これは、CRMに対して開発する場合、これはかなり正常なことです(ただし、自分の経験についてのみ話すことができます)。
  2. コーディング方法を本当に守ることが重要です。プロパティが存在することを確認し、適切な型にキャストしていることを確認してください。
  3. 例外をキャッチする必要がある場合は、それがになり、SoapException通常必要な情報がDetailプロパティに含まれます。これを覚えておくか、例外を確認して、全体を示しているわけではないと考えることが非常に重要です。
  4. CRMのカスタマイズに戻って、関係属性やデータ型などの名前を確認してください。簡単に参照できるように、開発を行っている間、必要なエンティティのカスタマイズウィンドウを開いておくのが好きです。
  5. FetchXML本当に強力ですが、本当に面倒です。あなたがそれを上手くやれば、あなたはたくさんの良いマイレージを得るでしょう-このようなツールは便利です。また、便利なトリック-CRM UIを介して高度な検索として必要なもの(または必要なものの例)を構築できる場合は、このトリックを使用して、使用したFetchXMLを取得できます。 GUIDなどを微調整する必要がありますが、ほとんどのクエリが作成されるため、コードでFetchXMLを使用する場合は、ビルディングブロックが提供されます。
  6. 展開環境によっては、プロキシなどを経由するかどうかに関係なく、使用される資格情報をいじくり回さなければならない場合があります...一般的なWeb参照のもの。CRMはそのようなことの影響を受けないということだけに注意する価値があります。ここでは実際のアドバイスはありません。過去に「楽しい」ことを引き起こしたので、メモを残してください。
于 2013-03-29T05:45:53.820 に答える