Christian Wagnsonner

Published: 79 articles

CRM 2016 – HTML Webresource – CustomScriptsFrame

Hallo Leute,

wie Ihr sicher mitbekommen habt, hat sich ja durch die Einführung der Turbo Forms im DOM Modell vieles getan (CRM 2015 Update 1).

Die folgenden Grafiken beschreiben die Umstellung des DOM Modells besser als viele Worte:

Damit einhergehend ändert sich auch der Zugriff auf Bibliotheken, welche auf dem Formular hinzugefügt/geladen werden, da sich ja diese Bibliotheken in das sog. „CustomScriptsFrame“ verlagern.
Achtung: Microsoft rät dazu, nicht direkt aus z.B. HTML WebResourcen auf diese Bibliotheken über das DOM zuzugreifen. Es wird empfohlen diese Bibliotheken direkt in der HTML WebResources referenzieren.

Das klingt in der Theorie natürlich schön und sollte auch so umgesetzt werden, in der Praxis gibt es jedoch auch alte Bibliotheken, die man nicht ohne weiteres umbauen kann oder darf.

Als weiteres Hindernis kommt dazu, dass die diversen Browser das DOM Modell anders aufbauen. Gibt es in Internet Explorer z.B. „contentIframe0“ und „contentIframe1“ (hier befindet sich das CustomScriptsFrame), gibt es z.B. in Chrome nur das „contentIframe0“ (hier befindet sich das CustomScriptsFrame).

Die Lösung ist relativ simple:

$(parent.document).find("iframe#customScriptsFrame")[0].contentWindow;

Aber achtet darauf: In Zukunft direkt einbinden und dieses Snippet nur im Notfall verwenden.

Beste Grüße
Chris

CRM 2016 – Online – Revise button not working

Hallo Leute,

ich bin auf ein spannendes Problem gestoßen. Im Zuge eines CRM 2016 Online Projekts (V 8.1.0.534) haben wir festgestellt, dass der Revise-Button (Überarbeiten-Button) auf der Entität Quote (Angebot) nicht funktioniert. Damit ist gemeint, dass sich absolut nichts tut, auch nicht im Hintergrund. Developer Toolbar und Fiddler haben keinerlei Aktivität festgestellt.

Um diesem Problem auf den Grund zu gehen, habe ich sämtliche Anpassungen (Attributes, Sections, etc.) vom Formular entfernt. Jedoch ließ sich der Button immer noch nicht klicken.

Im Zuge meiner Recherche bin ich auf folgenden Community-Forum-Eintrag gestoßen: ‚Revise‘ Quote
Darin wird folgender Lösungsansatz beschrieben:

Ben Tan responded on 12 Aug 2015 3:37 AM
Hi All
Many thanks for all your responses.
Having followed this up with Microsoft Support, it seems that this has been an issue before. Apparently, there were some fields missing from the entity which are required in order for the ‚Revise‘ function to work.
I was asked to add in the ‚Description‘ & ‚Freight Amount (Base)‘ fields into the form and this seems to have sorted the problem.
Seems a bit silly. If these are required fields then they should be locked on the form.
Anyway, the problem is fixed.
Thank you again for your time.

Tatsächlich! Das Hinzufügen der Attribute „Description“ und „Freight Amount (Base)“ hat das Problem gelöst. Die Felder können in irgendeiner Non-visible Section/Tab versteckt sein oder einfach nur ausgeblendet werden. Wichtig ist nur, dass sie sich in der Form-Definition befinden. Vielen Dank an den Benutzer Ben Tan!

Bis Demnächst!
Chris

CRM 2011/2013 – SoapLogger – SOAP Request aufbauen

Christian Wagnsonner 0 Comments

Hi Leute,

manchmal kommt man in die Verlegenheit einen SOAP-Request mittels Javascript aufbauen zu müssen. Das kann sich recht mühsam gestalten, z.B. mittels Fiddler.
Im SDK ist ein kleines Tool, der sog. „SoapLogger“, beigelegt.

In diesem Tool kann man wie gewohnt seinen Request, z.B. ExecuteWorkflowRequest, mit C#-Code schreiben und erhält in der Output.txt (im BIN-Verzeichnis) den fertigen Soap Request (und Response).
Ein Beispiel:

Man fügt der Klasse SOAPLogger.cs einfach folgenden Code hinzu:

SoapLoggerOrganizationService slos = new SoapLoggerOrganizationService(serverConfig.OrganizationUri, service, output);
                        
                        //Add the code you want to test here:
                        // You must use the SoapLoggerOrganizationService 'slos' proxy rather than the IOrganizationService proxy you would normally use.
                        ExecuteWorkflowRequest request = new ExecuteWorkflowRequest();
                        request.EntityId = new Guid("{D03006F4-2543-E411-9404-00155D1E15D1}");
                        request.WorkflowId = new Guid("{20D1417A-1DB5-475A-A751-7FF496E7CB7B}");
                        slos.Execute(request);

Ergebnis ist dann folgendes:

HTTP REQUEST
--------------------------------------------------
POST http://crmd-04/Swarovski/XRMServices/2011/Organization.svc/web
Content-Type: text/xml; charset=utf-8
SOAPAction: http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <request i:type="b:ExecuteWorkflowRequest" xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.microsoft.com/crm/2011/Contracts">
        <a:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
          <a:KeyValuePairOfstringanyType>
            <c:key>EntityId</c:key>
            <c:value i:type="d:guid" xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/">d03006f4-2543-e411-9404-00155d1e15d1</c:value>
          </a:KeyValuePairOfstringanyType>
          <a:KeyValuePairOfstringanyType>
            <c:key>WorkflowId</c:key>
            <c:value i:type="d:guid" xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/">20d1417a-1db5-475a-a751-7ff496e7cb7b</c:value>
          </a:KeyValuePairOfstringanyType>
        </a:Parameters>
        <a:RequestId i:nil="true" />
        <a:RequestName>ExecuteWorkflow</a:RequestName>
      </request>
    </Execute>
  </s:Body>
</s:Envelope>
--------------------------------------------------

HTTP RESPONSE
--------------------------------------------------
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <ExecuteResponse xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <ExecuteResult i:type="b:ExecuteWorkflowResponse" xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.microsoft.com/crm/2011/Contracts">
        <a:ResponseName>ExecuteWorkflow</a:ResponseName>
        <a:Results xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
          <a:KeyValuePairOfstringanyType>
            <c:key>Id</c:key>
            <c:value i:type="d:guid" xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/">00000000-0000-0000-0000-000000000000</c:value>
          </a:KeyValuePairOfstringanyType>
        </a:Results>
      </ExecuteResult>
    </ExecuteResponse>
  </s:Body>
</s:Envelope>
--------------------------------------------------

Cheers,
Christian

Windows 8.1 – HyperV – Virtuelle Switches

Hallo Leute,

heute möchte ich mit euch meine ersten Erfahrungen mit HyperV – insbesondere mit den virtuellen Switches – teilen. Zuerst die positive Überraschung: Windows 8.1 (wohl auch 8.0) bringt in der Professional Variante ein vollerwertiges Hyper V mit. Das finde ich erstaunlich, nimmt doch der direkte Konkurrent VMWare einiges an Kohle für die Workstation.

Das Anlegen einer Hyper V Maschine verläuft völlig unkompliziert, deshalb möchte ich euch hier auch gar nicht groß damit langweilen. Spannender wirds, wenns um die Netzwerkkonnektivität geht.
Sieht man sich nämlich die Einstellungen der virtuellen Maschine an, wirds etwas „komisch“:

2014-05-08 10_37_21-Hyper-V-Manager

Im besten Fall, habt Ihr links in der Navigation bereits einen Eintrag für die Netzwerkkarte. Ist das der Fall, dann habt Ihr im rechten Teil des Fensters einen Eintrag für „Virtueller Switch“, welcher auf „Nicht verbunden“ steht und auch nicht verändert werden kann. Habt Ihr keinen Eintrag für eine Netzwerkkarte, dann müsst Ihr auf „Hardware hinzufügen“ gehen und dort „Netzwerkkarte“ selektieren und auf „Hinzufügen“ klicken. Danach scheint der Eintrag in der Navigation links auf und wir können fortfahren.

Im Einstellungs-Dialog kann keine Netzwerkverbindung („Virtueller Switch“) angelegt werden. Dies passiert über das Hauptmenü, was auch Sinn macht, denn diese Switches können für sämtliche virtuellen Maschinen verwendet werden.

2014-05-08 10_41_06-Hyper-V-Manager

2014-05-08 10_42_19-Manager für virtuelle Switches für _FWI-NOTE-WACH_

Nun hat man die Qual der Wahl.

Extern: Bedeutet, dass die virtuellen Maschinen mit dem Host (und untereinander) verbunden sind und die Internetverbindung des Hosts verwenden dürfen.
Intern: Bedeutet, dass die virtuellen Maschinen mit dem Host (und untereinander) verbunden sind, jedoch KEINE Internetverbindung (bzw. Verbindung zum Netzwerk des Hosts) haben.
Privat: Bedeutet, dass die virtuellen Maschinen untereinander verbunden sind, jedoch nicht mit dem Host.

Wählt die für euch passende Option aus und klickt auf „virtuellen Switch hinzufügen“. Man gelangt jetzt automatisch zu den Einstellungen des virtuellen Switches:

2014-05-08 10_46_12-Hyper-V-Manager

Wie Ihr seht, kann man hier nochmals den Verbindungstyp ändern. Bei internen oder privaten Switches muss man hier nichts mehr machen. Bei der Option „Extern“ muss man noch die Netzwerkkarte auswählen, welche geteilt werden soll.

Abschließend drückt Ihr auf Anwenden oder schlicht auf OK und geht wieder in das Hauptmenü des Hyper-V-Managers und wählt eure virtuelle Maschine aus und geht in deren Einstellungen.

2014-05-08 10_49_36-Hyper-V-Manager

Dort, wo eben noch ausschließlich „Nicht verbunden“ zur Auswahl stand, könnt Ihr nun auf euren Switch zugreifen. Wählt ihn aus, klickt OK und fährt eure virtuelle Maschine hoch.

ACHTUNG: Es ist empfehlenswert alle diese Schritte zu tätigen, während die Maschine abgeschalten ist. Im laufenden Betrieb ziehen die Einstellungen, auch nach Neustart!, nicht.

Cheers,
Christian

CRM 2013 – AddNotification – (mehrere) Benachrichtigung auf Form hinzufügen

Hallo Leute,

heute möchte ich euch zeigen, wie Ihr ganz einfach eine Notification in einem CRM Form hinzufügen könnt. Das nachfolgende Beispiel ist für CRM 2013, wenn man den ReleaseNotes glauben schenken darf, dann sollte das Beispiel auch für 2011 funktionieren, das habe ich jedoch nicht getestet.

Was verwende ich? Nachdem ich nicht direkt ins DOM gehen wollte, habe ich auf das XrmServiceToolkit zurückgegriffen. Dieses bietet die Funktion „AddNotification“ an:

var addNotification = function(message, level) {
        /// <summary>
        /// Add a notification bar message with CRM 2011 style
        /// </summary>
        /// <param name="message" type="string">
        /// Details of the message
        /// </param>
        /// <param name="level" type="int">
        /// The warning level of the message: [1 critical, 2 information, 3 warning]
        /// </param>
        /// <returns type="void" />

.....
}

Diese Methode ruft man dann einfach auf, z.B. mit

XrmServiceToolkit.Common.AddNotification(message, severity);

Jetzt gibt es aber noch ein Problem: Möchte man mehrere Notifications, zur selben Severity, auf einem Form anzeigen, so wird immer nur die letzte angezeigt. Weitergeholfen hat mir Sy’s Blog, aus dem hervor geht, dass CRM 2013/11 durchaus mehrerer solcher Notifications darstellen kann. Sieht man sich den Code des XrmServiceToolkits genauer an, sieht man, dass je nach Severity hardcoded ID’s für eine Notification erstellt werden. Severity 1 = „mep1“, Severity 2 = „mep2“, Severity 3 = „mep3“:

var addNotification = function (message, level) {
        /// <summary>
        /// Add a notification bar message with CRM 2011 style
        /// </summary>
        /// <param name="message" type="string">
        /// Details of the message
        /// </param>
        /// <param name="level" type="int">
        /// The warning level of the message: [1 critical, 2 information, 3 warning]
        /// </param>
        /// <returns type="void" />
        var notificationsArea = document.getElementById('crmNotifications');
        if (notificationsArea === null || notificationsArea === undefined) {
            alert('Cannot find the notification area'); return;
        }
        if (typeof notificationsArea.AddNotification !== "undefined" && typeof notificationsArea.control.AddNotification !== "undefined") {
            alert('Add Notification is no longer supported'); return;
        }
        if (level === 1) {
            //critical
            if (typeof notificationsArea.AddNotification !== "undefined") {
                notificationsArea.AddNotification('mep1', 1, 'source', message);
            } else if (typeof notificationsArea.control.AddNotification !== "undefined") {
                notificationsArea.control.AddNotification('mep1', 1, 'source', message);
            }
        }

        if (level === 2) {
            //Info
            if (typeof notificationsArea.AddNotification !== "undefined") {
                notificationsArea.AddNotification('mep3', 3, 'source', message);
            } else if (typeof notificationsArea.control.AddNotification !== "undefined") {
                notificationsArea.control.AddNotification('mep3', 3, 'source', message);
            }
        }
        if (level === 3) {
            //Warning
            if (typeof notificationsArea.AddNotification !== "undefined") {
                notificationsArea.AddNotification('mep2', 2, 'source', message);
            } else if (typeof notificationsArea.control.AddNotification !== "undefined") {
                notificationsArea.control.AddNotification('mep2', 2, 'source', message);
            }
        }
        if (message === "") {
            if (typeof notificationsArea.SetNotifications !== "undefined") {
                notificationsArea.SetNotifications(null, null);
            } else if (typeof notificationsArea.control.SetNotifications !== "undefined") {
                notificationsArea.control.SetNotifications(null, null);
            } else {
                alert('Set Notification is no longer supported');
            }
        }
    };

Es ist also durchaus möglich mehrere Notifications zu setzen, jedoch nur eine pro Severity. Das hat in meinem Fall jedoch nicht ausgereicht. Ich habe die Methode einfach umgebaut, so dass man dem Aufruf eine ID mitgeben kann. Wird diese nicht mitgegeben, wird weiterhin „mep1“, „mep2“, etc. verwendet.

var addNotification = function(message, level, uniqueName) {
        /// <summary>
        /// Add a notification bar message with CRM 2011 style
        /// </summary>
        /// <param name="message" type="string">
        /// Details of the message
        /// </param>
        /// <param name="level" type="int">
        /// The warning level of the message: [1 critical, 2 information, 3 warning]
        /// </param>
        /// <returns type="void" />

        if (uniqueName == undefined) {
            uniqueName = "mep" + level;
        }

        var notificationsArea = document.getElementById('crmNotifications');
        if (notificationsArea === null || notificationsArea === undefined) {
            alert('Cannot find the notification area'); return;
        }
        if (typeof notificationsArea.AddNotification !== "undefined" && typeof notificationsArea.control.AddNotification !== "undefined") {
            alert('Add Notification is no longer supported'); return;
        }
        if (level === 1) {
            //critical
            if (typeof notificationsArea.AddNotification !== "undefined") {
                notificationsArea.AddNotification(uniqueName, 1, 'source', message);
            } else if (typeof notificationsArea.control.AddNotification !== "undefined") {
                notificationsArea.control.AddNotification(uniqueName, 1, 'source', message);
            }
        }

        if (level === 2) {
            //Info
            if (typeof notificationsArea.AddNotification !== "undefined") {
                notificationsArea.AddNotification(uniqueName, 3, 'source', message);
            } else if (typeof notificationsArea.control.AddNotification !== "undefined") {
                notificationsArea.control.AddNotification(uniqueName, 3, 'source', message);
            }
        }
        if (level === 3) {
            //Warning
            if (typeof notificationsArea.AddNotification !== "undefined") {
                notificationsArea.AddNotification(uniqueName, 2, 'source', message);
            } else if (typeof notificationsArea.control.AddNotification !== "undefined") {
                notificationsArea.control.AddNotification(uniqueName, 2, 'source', message);
            }
        }
        if (message === "") {
            if (typeof notificationsArea.SetNotifications !== "undefined") {
                notificationsArea.SetNotifications(null, null);
            } else if (typeof notificationsArea.control.SetNotifications !== "undefined") {
                notificationsArea.control.SetNotifications(null, null);
            } else {
                alert('Set Notification is no longer supported');
            }
        }
    };

Das Resultat ist nun folgendes:

2014-04-29 10_29_15-Preiserhebung_ Neu_ Preiserhebung - Microsoft Dynamics CRM

Cheers,
Chris

CRM 2013 – Real Time Workflows / Synchrone Workflows

Hallo Leute,

eine Neuerung mit CRM 2013 sind synchrone Workflows. Lief in CRM 2011 ein Workflow immer im Hintergrund, so lässt sich in 2013 nun direkt auf eine Benutzer-Aktion reagieren.
Wie das funktioniert möchte ich euch an Hand eines Beispiels erklären.

Ihr befindet euch auf der Account-Entität und habt ein Feld (z.B. AccountNumber) ausgefüllt und die Entität gespeichert. Nun soll sichergestellt sein, dass Accounts, welche eine Accountnumber besitzen, nicht mehr deaktiviert werden können. Und das nicht nur mittels Oberflächenanpassungen… Hier kommen die synchronen Workflows ins Spiel.

1.) Fügt einen neuen Workflow hinzu. Unbedingt beachten: Den Workflow NICHT IM HINTERGRUND AUSFÜHREN!!!
2014-03-28 10_58_17-Process_ Deactivate Account - Microsoft Dynamics CRM

2.) Beachtet bei den folgenden Einstellungen den „Scope“ und „Start when“. Nachdem das Deaktivieren niemals möglich sein soll, ist als Scope „Organization“ einzustellen. Bei „Start when“ ist die Option „Record status changes“ auszuwählen (Status = aktiv/inaktiv).
2014-03-28 10_59_53-Neuen Beitrag erstellen ‹ wagnsonner.com — WordPress

3.) Nun gehts ans Designen des Workflows
3.1) Zuerst prüfe ich, ob der Datensatz überhaupt deaktiviert werden soll (es könnte ja auch ein inaktiver aktiviert werden)
3.2) Überprüfung ob die AccountNumber gesetzt ist
3.3) Falls ja, wird der Workflow mit einem Fehler beendet
2014-03-28 11_04_33-Neuen Beitrag erstellen ‹ wagnsonner.com — WordPress

4.) Zu guter letzt soll natürlich auch die Meldung definiert werden, die der Anwender im Fehlerfall bekommt. Dafür klickt auf „Set properties“.
2014-03-28 11_06_38-Process_ Deactivate Account - Microsoft Dynamics CRM

5.) Speichern, Workflow aktivieren und testen:
2014-03-28 11_08_08-Account_ Bridonncw12222223dsdfsdf2222 - Microsoft Dynamics CRM

Viel Spaß beim nachmachen.
Cheers,
Chris

CRM 2013 – Ribbon Button im Offline-Modus ausblenden

Es gibt Funktionen im CRM (z.B. Prozesse) welche Offline nicht verfügbar sind. Das ist natürlich unpraktisch, wenn man z.B. via Javascript einen Process-Dialog öffnet, der dann klarerweise versagt.

Dafür gibt es die DisplayRule „CrmOfflineAccessStateRule“:

<DisplayRule Id="xxxx">
<CrmOfflineAccessStateRule
   Default="true"
   State="Online"  />
</DisplayRule> 

Die Beispielkonfiguration verursacht, dass der Button nur noch ONLINE angezeigt wird. Die gleiche Rule/Parameter könnt Ihr natürlich in 3rd Party Tooles (z.B. RibbonWorkBench) auch verwenden.

Cheers,
Christian

CRM 2013 – Navigations Favoriten

Heute möchte ich euch zeigen, wie Ihr ganz einfach ein paar Bookmarks anlegen könnt, um direkt auf z.B. die Übersicht aller Accounts oder Solutions zu gelangen. Auch zum Öffnen der Advanced Find zeige ich euch einen kleinen „Hack“.

Wenn man neu in CRM 2013 einsteigt, kann einem die Navigation schon mal vor Herausforderungen stellen. Die Url’s in der Addressbar sind dann auch nicht gerade hilfreich, der Code nach der main.aspx (z.B. main.aspx#506253661) ist nur für die aktuelle Browsersession gültig.

Zum Glück gibts eine einfache Möglichkeit:

2014-03-21 10_04_24-Greenshot capture form

Das könnt Ihr nun für jede Entität anwenden und euch die URL einfach in die Bookmarks tun.

Für die Advanced Find, die sich ja nicht mehr auf jeder Ansicht verbirgt, ist es ein wenig komplizierter. Hier muss ein Javascript Codestück als Bookmark gespeichert werden. Natürlich muss zuvor die CRM Instanz bereits offen sein.

javascript:window.open($('#crmContentPanel iframe:not([style*=\"visibility: hidden\"])')[0].contentWindow.Xrm.Page.context.getClientUrl() + "/main.aspx?pagetype=advancedfind");

Cheers,
Christian

CRM 2013 – Entity Relationship Diagram (ERD) erzeugen

Hallo Leute,

wenn man in ein bereits laufendes Projekt, mit vielen Custom Entities, einsteigt, tut man sich oft schwer die Abhängigkeiten zwischen den Entitäten zu verstehen. Im besten Fall gibt’s eine Beschreibung des Datenmodells, ein Relationship-Diagram meistens nicht.

In der SDK versteckt sich ein Tool Namens „MetaDataDiagram“ (SDK\SampleCode\CS\Metadata\Diagram), welches euch ein Visio-Diagramm erzeugt. Einfach das Projekte mit Visual Studio öffnen und mit F5 ausführen. Das Diagramm wird dann als Visio-Datei im BIN-Verzeichnis abgelegt. Achtung: Das kann sehr lange andauern (je nach Größe eurer Solution) und ist ziemlich ressourcenintensiv. Es empfiehlt sich, das nicht unbedingt unter Tags zu machen 😉

Beispiel:

erd_diagram

Cheers,

Christian

CRM 2013 – Bulk Update/Create – ExecuteMultipleRequest

Hallo Leute,

willkommen im neuen Jahr 😉 Hat ein wenig gedauert bis zu meinem ersten Blog-Eintrag im neuen Jahr.

Heute möchte ich euch zeigen, wie man schnell und einfach mehrere Entitäten updated, ohne mehrere Webservice-Calls auslösen zu müssen. Mit CRM 2013 (auch CRM 2011 mit einem der letzten Update Rollups) besteht nun die Möglichkeit einen sog. „ExecuteMultipleRequest“ durchzuführen. Diesem Objekt fügt man einfach mehrere Requests hinzu (UpdateRequest, CreateRequest) und führt ihn aus. Seht selbst:

/// <summary>
        /// Setzt alle übergebenen XyzCode zu IsDefault = false
        /// </summary>
        /// <param name="serviceContext">The service context.</param>
        /// <param name="XyzCode">The sap codes.</param>
        /// <exception cref="Microsoft.Xrm.Sdk.InvalidPluginExecutionException">
        /// </exception>
        private void SetXyzCodeToDefaultFalse(XrmServiceContext serviceContext, List<xxx_SapCode> XyzCode)
        {

            if (XyzCode.Any())
            {
                var bulkRequest = new ExecuteMultipleRequest()
                {
                    // Assign settings that define execution behavior: continue on error, return responses. 
                    Settings = new ExecuteMultipleSettings()
                    {
                        ContinueOnError = false,
                        ReturnResponses = true
                    },

                    // Create an empty organization request collection.
                    Requests = new OrganizationRequestCollection()
                };

                foreach (var sapCode in XyzCode)
                {
                    var sapCodeToUpdate = new xxx_SapCode
                    {
                        Id = sapCode.Id,
                        xxx_IsDefault = false,
                        EntityState =  EntityState.Changed
                    };

                    // updated entity must be added to a updateMultipleRequest
                    var updateRequest = new UpdateRequest() {Target = sapCodeToUpdate};
                    bulkRequest.Requests.Add(updateRequest);
                }
                
                // Execute all the requests in the request collection using a single web method call.
                var bulkResponse = (ExecuteMultipleResponse) serviceContext.Execute(bulkRequest);

                var sbErrorMessage = new StringBuilder();

                // There should be no responses unless there was an error. Only the first error 
                // should be returned. That is the behavior defined in the settings.
                if (bulkResponse.Responses.Count > 0)
                {
                    foreach (var responseItem in bulkResponse.Responses)
                    {
                        if (responseItem.Fault != null)
                        {
                            var requestItem = bulkRequest.Requests[responseItem.RequestIndex] as UpdateRequest;
                            if (requestItem == null)
                                throw new InvalidPluginExecutionException(
                                    string.Format("RequestItem for ResponseIndex {0} was not found.",
                                        responseItem.RequestIndex));

                            var updateItem = requestItem.Target as xxx_SapCode;
                        }
                    }
                }

                if (sbErrorMessage.ToString().Length > 0)
                {
                    sbErrorMessage.Insert(0,
                        "There occured an error while updating following items: " + Environment.NewLine);

                    throw new InvalidPluginExecutionException(sbErrorMessage.ToString());
                }
            }
        }

Wichtig ist noch, dass man den Response analysiert und ggf. aufgetretene Fehler abfängt. Dafür sieht man sich responseItem.Fault an (Fault = true = Fehler) Ich erledige das am einfachsten mit einem StringBuilder-Objekt, dem ich nach und nach die Fehlermeldungen hinzufüge (responseItem.Fault.Message).

Gutes Gelingen!

Cheers,
Chris