Results for tag "dynamics-crm"

CRM 2011 – N:M Beziehung via C# setzen

CRM 2011 beherrscht nun endlich eine vernünftige N:M Beziehung.
Folgendes Code-Snippet zeigt, wie man via C# und dem SDK-Webservice zwei Entitäten verbindet.

[sourcecode language=“csharp“]
/// <summary>
/// Clones the entity.
/// </summary>
/// <param name="organization">The organization.</param>
/// <param name="entity1">The entity1.</param>
/// <param name="entity2">The entity2.</param>
/// <param name="relationShipName">Name of the relation ship.</param>
public static void AssociateEntities(contracts.IOrganization organization, Entity entity1, Entity entity2, string relationShipName)
{
// Create an AssociateEntities request
AssociateEntitiesRequest request = new AssociateEntitiesRequest();

// Set the ID of Moniker1 to the ID of the lead.
request.Moniker1 = new EntityReference { Id = entity1.Id, LogicalName = entity1.LogicalName};

// Set the ID of Moniker2 to the ID of the contact.
request.Moniker2 = new EntityReference { Id = entity2.Id, LogicalName = entity2.LogicalName};

// Set the relationship name to associate on.
request.RelationshipName = relationShipName;

// Execute the request.
Helper.OrgService(organization).Execute(request);
}
[/sourcecode]

Viel Spaß beim Ausprobieren!

CRM 2011 – Verbundene Objekte finden/laden (Get related Entities)

Verbindungen zwischen Entitäten werden in CRM, wie in einer Datenbank, mittels Referenzen abgebildet. Das funktioniert im GUI bzw. im Customizing (welches direkt im CRM passiert) auch sehr gut. Wenn man mittels C#-SDK versucht Entitäten zu bearbeiten/erstellen wird es dann schnell etwas umständlich.

Jeder kennt z.B. folgendes Beispiel: Angebot –> Positionen (1:n). Über die Eigenschaften (Attributes) der Angebot-Entität lässt sich nun nicht mehr ermitteln, wieviele Positionen das Angebot nun besitzt. Um diese Aufgabe zu lösen, habe ich mir zwei Helper-Methoden geschrieben, welche mir eine Collection der (Positionen-)Entitäten oder nur deren PrimaryKeys zurückliefern:

[sourcecode language=“csharp“]
/// <summary>
/// Gets the related projekt standorte.
/// </summary>
/// <param name="organization">The organization.</param>
/// <param name="entityName">Name of the entity.</param>
/// <param name="foreignKeyFieldName">Name of the foreign key field.</param>
/// <param name="foreignKeyFieldValue">The foreign key field value.</param>
/// <param name="columnSet">The column set.</param>
/// <returns></returns>
public static EntityCollection GetRelatedEntities(IOrganization organization, string entityName, string foreignKeyFieldName, string foreignKeyFieldValue, ColumnSet columnSet)
{
ConditionExpression condition = new ConditionExpression();
condition.AttributeName = foreignKeyFieldName;
condition.Operator = ConditionOperator.Equal;
condition.Values.Add(foreignKeyFieldValue);

// Create query expression.
QueryExpression query1 = new QueryExpression();
query1.ColumnSet = columnSet;
query1.EntityName = entityName;
query1.Criteria.AddCondition(condition);

EntityCollection result1 = Helper.OrgService(organization).RetrieveMultiple(query1);

return result1;
}

/// <summary>
/// Gets the related object ids.
/// </summary>
/// <param name="organization">The organization.</param>
/// <param name="entityId">The entity id.</param>
/// <param name="relatedEntityName">Name of the related entity.</param>
/// <param name="relatedEntityPrimaryKeyName">Name of the related entity primary key.</param>
/// <param name="foreignKeyName">Name of the foreign key.</param>
/// <returns></returns>
public static List<string> GetRelatedObjectIds(IOrganization organization, string entityId, string relatedEntityName, string relatedEntityPrimaryKeyName, string foreignKeyName)
{
var relEntities = EntityHelper.GetRelatedEntities(organization, relatedEntityName, foreignKeyName,
entityId, new ColumnSet(relatedEntityPrimaryKeyName));

return relEntities.Entities.Select(entity => entity.Attributes[relatedEntityPrimaryKeyName].ToString()).ToList();
}
[/sourcecode]

Ich hoffe ich konnte euch damit die Arbeit ein wenig verkürzen 😉

Cheers,
Chris

CRM 2011 – Nummerngenerierung (Laufnummer erzeugen)

Leider ein sehr leidiges Thema in der CRM-Entwicklung. Microsoft arbeitet mit GUIDs als Primärschlüssel, was einerseits natürlich sinnvoll ist, andererseits keinen „Schlüssel“ bzw. „Autowert“ zur Verfügung stellt, mit welchem man arbeiten kann. CRM bietet stattdessen die sog. „Automatische Nummerierung“ an. Leider hat diese Funktionalität einen riesen Haken: Die Funktionalität steht nur für die Entitäten Verträge, Anfragen, Angebote, Aufträge, Artikel, Rechnungen und Kampagnen zur Verfügung.

Bei einem größeren Projekt, z.B. wenn man komplett eigene Geschäftsprozesse, ist das natürlich ein wahnsinn. Keine Ahnung was Microsoft sich da hat einfallen lassen. Vermutlich kommt hier das übliche Argument zum tragen: „Für 95% der Kunden reicht’s aus. Wir wollen unseren Implementierungspartnern nicht die komplette Arbeit abnehmen.“. Naja, ich war noch nie ein großer Fan dieser Strategie…

Um das Problem zu lösen, habe ich eine eigene Entität erstellt, welche ich „Laufnummer“ genannt habe. Diese Entität hat keine Ansichten/Formulare, sie besitzt lediglich die Felder „neu_laufnummerid“ und „neu_nummer“.

Nachdem ich in meinen CRM-Projekten stark auf Webservices setze, habe ich eine Klasse zum Erzeugen der Laufnummer (nach Schema JJMMTT001) entwickelt, welche diese Nummer für den aktuellen Tag (in der Entität) sucht und ggf. hochzählt. Falls die Laufnummer (für diesen Tag) noch nicht generiert wurde, wird sie natürlich neu erzeugt:

[sourcecode language=“csharp“]
public class Laufnummer
{
public const string EntityName = "neu_laufnummer";
public const string EntityPrimaryKey = "neu_laufnummerid";

/// <summary>
/// Finds the highest verkaufsprojekt nummer.
/// </summary>
/// <param name="organization">The organization.</param>
/// <returns></returns>
public static string GetNew(contracts.IOrganization organization)
{
string laufNummer;
string suffix = StringHelper.FillLeftZero(1, 3);

QueryExpression query = new QueryExpression();
query.ColumnSet.AddColumns("neu_laufnummerid", "neu_nummer");
query.EntityName = EntityName;
query.AddOrder("neu_nummer", OrderType.Descending);
query.PageInfo = new PagingInfo();
query.PageInfo.Count = 1;
query.PageInfo.PageNumber = 1;

EntityCollection results = Helper.OrgService(organization).RetrieveMultiple(query);

if (results.Entities.Count > 0)
{
string lastNumber = results[0].Attributes["wit_nummer"].ToString();
if (lastNumber.Length < 9)
{
throw new InvalidProgramException("Die letzte Laufnummer war weniger als 9 Zeichen lang!");
}

string lastDayPattern = lastNumber.Substring(0, 6);
int lastCount = Convert.ToInt32(lastNumber.Substring(6, 3));
if (lastDayPattern == GetCurrentDayPattern())
{
lastCount++;
laufNummer = lastDayPattern + utils.StringHelper.FillLeftZero(lastCount, 3);
}
else
{
laufNummer = GetCurrentDayPattern() + suffix;
}

}
else
{
laufNummer = GetCurrentDayPattern() + suffix;
}

var entity = new Entity(EntityName);
entity.Attributes["neu_nummer"] = laufNummer;
EntityHelper.CreateEntity(organization, entity);

return laufNummer;
}

/// <summary>
/// Gets the current day pattern.
/// </summary>
/// <returns></returns>
public static string GetCurrentDayPattern()
{
return utils.StringHelper.FillLeftZero(Convert.ToInt32(DateTime.Now.Year.ToString().Substring(2, 2)), 2) +
utils.StringHelper.FillLeftZero(DateTime.Now.Month, 2) +
utils.StringHelper.FillLeftZero(DateTime.Now.Day, 2);
}
[/sourcecode]

Dieses Webservice kann man nun z.B. im OnSave-Event der Entität oder durch einen sonstigen Event triggern.
Viel Spaß beim Anwenden.

Cheers,
Chris

CRM 2011 – jQueryUI Dialog verwenden

Was in normalen Web Applikationen ganz easy von der Hand geht, wird in CRM 2011 etwas schwierig. Wie bindet man z.B. das CSS der UI-Controls ein? Der Designer lässt ein Einbinden (wie bei den JS-Webresourcen) nämlich nicht zu. Auch kann man nicht so einfach vorgehen, wie in der jQueryUI-Dokumentation beschrieben.

Dort wird z.B. folgendes Beispiel für einen Modal-Dialog beschrieben:

[sourcecode language=“html“]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery UI Dialog – Basic modal</title>
<link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">
<script src="../../jquery-1.6.2.js"></script>
<script src="../../external/jquery.bgiframe-2.1.2.js"></script>
<script src="../../ui/jquery.ui.core.js"></script>
<script src="../../ui/jquery.ui.widget.js"></script>
<script src="../../ui/jquery.ui.mouse.js"></script>
<script src="../../ui/jquery.ui.draggable.js"></script>
<script src="../../ui/jquery.ui.position.js"></script>
<script src="../../ui/jquery.ui.resizable.js"></script>
<script src="../../ui/jquery.ui.dialog.js"></script>
<link rel="stylesheet" href="../demos.css">
<script>
$(function() {
// a workaround for a flaw in the demo system (http://dev.jqueryui.com/ticket/4375), ignore!
$( "#dialog:ui-dialog" ).dialog( "destroy" );

$( "#dialog-modal" ).dialog({
height: 140,
modal: true
});
});
</script>
</head>
<body>

<div class="demo">

<div id="dialog-modal" title="Basic modal dialog">
<p>Adding the modal overlay screen makes the dialog look more prominent because it dims out the page content.</p>
</div>

<!– Sample page content to illustrate the layering of the dialog –>
<div class="hiddenInViewSource" style="padding:20px;">
<p>Sed vel diam id libero <a href="http://example.com">rutrum convallis</a>. Donec aliquet leo vel magna. Phasellus rhoncus faucibus ante. Etiam bibendum, enim faucibus aliquet rhoncus, arcu felis ultricies neque, sit amet auctor elit eros a lectus.</p>
<form>
<input value="text input" /><br />
<input type="checkbox" />checkbox<br />
<input type="radio" />radio<br />
<select>
<option>select</option>
</select><br /><br />
<textarea>textarea</textarea><br />
</form>
</div><!– End sample page content –>

</div><!– End demo –>

</body>
</html>

[/sourcecode]

Folgende Probleme stellen sich nun:
1.) Einbinden der JS-Files. Das geht gerade noch problemlos. Man muss lediglich Webresourcen vom Typ JScript hinzufügen (z.B. 1 für Jquery, 1 für JQueryUI; Für einen einfachen Dialog werden nur diese beiden Bibliotheken benötigt!) und diese im gewünschten Formular einfügen.

2.) Script-Block: Hier legt man ebenfalls am einfachsten eine Webresource vom Typ JScript an und ordnet sie dem Formular zu.

3.) Stylesheet: Man kann zwar eine Webresource vom Typ Stylesheet anlegen (wie bei JScript), diese aber nicht einem Fomular zuordnen. Das muss man selbst in einer Javascript-Webresource machen.

4.) Der HTML-Block mit der der ID „modal-dialog“ muss ins Form. Aber auch das ist nicht so ohne weiteres möglich. Auch hier ist es notwendig, den notwendigen HTML-Code in einer Javascript-Webresource zu erzeugen.

Daher ein kurzes Tutorial bzw. meine Lösung.

1.) Legt in eurer Solution zwei Webresourcen an und nennt diese am besten {euerprefix}_jQuery und {euerprefix}_jQueryUI und kopiert den Inhalt der Dateien jquery-1.6.2.min.js bzw. jquery-ui-1.8.16.custom.min.js in den Text-Editor.

2.) Legt eine weitere Resource, diesmal vom Typ „Stylesheet“ an und kopiert in diese den Inhalt der Datei jquery-ui-1.8.16.custom.css. Am unteren Ende des Dialogs steht eine URL. Diese bitte notieren.

3.) Legt eine weitere Webresource an und nennt diese {euerprefix}_{FormularName}.
In diese Resource habe ich die Funktionalitäten eingefügt, welche a) das Stylesheet laden und b) den notwendigen HTML-Code aufbauen.

[sourcecode language=“javascript“]
this.HideDialog = function () {
$("#dialog-modal").dialog(‚close‘)
}

// erstellt und öffnet den dialog
this.ShowDialog = function (title, message, detailsMessage, hideOkButton) {

// load stylesheet to head
var link = $("<link>");
link.attr({
type: ‚text/css‘,
rel: ’stylesheet‘,
<strong> href: "URL zum Stylesheet"</strong>
});
$("head").append(link);

var wrapper = $.create(‚div‘, { ‚id‘: ‚dialog-modal‘, ‚title‘: title }, []);
var text = $.create(’span‘, { ‚id‘: ‚dialogText‘ }, []);
text.innerHTML = message;
$(wrapper).append(text);

$(wrapper).append($.create(‚hr‘, {}, []));

var details = $.create(‚div‘, { ‚id‘: ‚divDialogDetails‘, ’style‘: ‚margin:3px; padding:7px; font-size:12px; display:block; background-color:#eeeeee; width:100%; height:100px;‘ }, []);
details.innerHTML = detailsMessage;
$(wrapper).append(details);

document.getElementById(‚tdAreas‘).appendChild(wrapper);

if (hideOkButton) {
$("#dialog-modal").dialog({
height: 400,
width: 400,
modal: true,
draggable: false,
resizable: false

});
}
else {
$("#dialog-modal").dialog({
height: 400,
width: 400,
modal: true,
draggable: false,
resizable: false,
buttons: { "schließen": function () { $(this).dialog("close"); } }
});
}
}
[/sourcecode]
Fügt bitte an der markierten Stelle die URL zu eurem Stylesheet ein!

$.create ruft ein jQuery-Plugin zum Erzeugen der DOM-Elemente auf. Ich habe den Code für das Plugin direkt in die Jquery-Webresource kopiert. Das könnt Ihr auch machen, oder legt einfach eine weitere JS-Resource an. Der Code für das Plugin lautet:

[sourcecode language=“javascript“]

// JQUERY Plugin zum Erzeugen von DOM-Elementen
jQuery.create = function () {
if (arguments.length == 0) return [];
var args = arguments[0] || {}, elem = null, elements = null;
var siblings = null;

// In case someone passes in a null object,
// assume that they want an empty string.
if (args == null) args = "";
if (args.constructor == String) {
if (arguments.length > 1) {
var attributes = arguments[1];
if (attributes.constructor == String) {
elem = document.createTextNode(args);
elements = [];
elements.push(elem);
siblings =
jQuery.create.apply(null, Array.prototype.slice.call(arguments, 1));
elements = elements.concat(siblings);
return elements;

} else {
elem = document.createElement(args);

// Set element attributes.
var attributes = arguments[1];
for (var attr in attributes)
jQuery(elem).attr(attr, attributes[attr]);

// Add children of this element.
var children = arguments[2];
children = jQuery.create.apply(null, children);
jQuery(elem).append(children);

// If there are more siblings, render those too.
if (arguments.length > 3) {
siblings =
jQuery.create.apply(null, Array.prototype.slice.call(arguments, 3));
return [elem].concat(siblings);
}
return elem;
}
} else return document.createTextNode(args);
} else {
elements = [];
elements.push(args);
siblings =
jQuery.create.apply(null, (Array.prototype.slice.call(arguments, 1)));
elements = elements.concat(siblings);
return elements;
}
};
[/sourcecode]

Die erzeugten HTML-Elemente werden dem DOM-Element „tdAreas“ hinzugefügt, welches jedes Formular hat und den Content des Forms hält. Wenn Ihr den Dialog auch anderswo verwenden möchtet, nehmt darauf Rücksicht!

So und zu guter letzt könnt Ihr den Dialog auch noch verwenden, wie seht Ihr hier…:

[sourcecode language=“javascript“]
ShowDialog(‚Titel‘, ‚Kurzbeschreibung‘, ‚längere Beschreibung‘);
HideDialog();
[/sourcecode]

Cheers,
Chris

CRM 2011 – Ribbon Button hinzufügen + Custom Rule

Christian Wagnsonner 1 Comment

Eine gängige Anforderung in einem CRM-Projekt ist es div. Schaltflächen den Ribbons hinzuzufügen, auszublenden oder zu aktivieren/deaktivieren.

Ich zeige euch in diesem Blog, wie man einen Button hinzufügt, der nur sichtbar ist, wenn die aufgerufene Entität bereits über eine ID verfügt (also bereits gespeichert wurde).

Zunächst muss man der Solution eine „Clienterweiterung“ hinzufügen, nämlich die „Anwendungsmenübänder“. Das geht wie folgt:

Danach ist es notwendig die Solution zu exportieren. Die heruntergeladene ZIP-Datei müsst Ihr irgendwohin extrahieren und die Datei „customizations.xml“ mit einem Editor öffnen. Ich empfehle euch dies direkt im Visual Studio zu tun, dort kann man auch recht einfach die Schema-Dateien zur Validierung einbinden. Wie das funktioniert, erfährt Ihr in folgendem Blog: Customizations XML validieren

Sucht nun in der Datei nach der Entität, welcher Ihr den Ribbon-Button hinzufügen möchtet. Solltet Ihr diese nicht finden, müsst Ihr diese zuerst der Solution hinzufügen und den Export wiederholen.

[sourcecode language=“xml“]
<Entity>
<Name LocalizedName="Lead" OriginalName="Lead">Lead</Name>
<ObjectTypeCode>4</ObjectTypeCode>
<strong><RibbonDiffXml></strong>
<CustomActions />
<Templates>
<RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
</Templates>
<CommandDefinitions />
<strong><RuleDefinitions></strong>
<TabDisplayRules />
<DisplayRules />
<strong><EnableRules /></strong>
</RuleDefinitions>
<LocLabels />
</RibbonDiffXml>
</Entity>
[/sourcecode]

Das Beispiel zeigt eine Standard-Entität. Für dieses Beispiel ist nur die Bearbeitung der „RibbonDiffXml“-Sektion notwendig.
Zunächst fügt man eine CustomAction hinzu:

[sourcecode language=“xml“]
<CustomAction Id="Mscrm.Isv.wit_schnellerfassung.FormCustomAction" Location="Mscrm.Form.wit_schnellerfassung.MainTab.Groups._children" Sequence="6000">
<CommandUIDefinition>
<Group Id="Mscrm.Isv.wit_schnellerfassung.Form.Group0" Sequence="400" Command="Mscrm.Isv.wit_schnellerfassung.Form.Group0" Title="$LocLabels:CustomGroup.Title" Description="$LocLabels:CustomGroup.Description" Template="Mscrm.Templates.3.3">
<Controls Id="Mscrm.Isv.wit_schnellerfassung.Form.Group0.Controls">
<Button Id="Mscrm.Isv.wit_schnellerfassung.Form.Group0.Control0" Sequence="10" <strong>Command="Mscrm.Isv.wit_schnellerfassung.Form.Group0.Control0"</strong> Image16by16="$webresource:wit_icon_schnellerfassung" Image32by32="$webresource:wit_icon_schnellerfassung" TemplateAlias="o1"></Button>
</Controls>
</Group>
</CommandUIDefinition>
</CustomAction>
</CustomActions>
[/sourcecode]
Achtet auf die fett markierte Stelle. Der Command ist für das Hinzufügen der Rules (aktivieren/deaktivieren) wichtig. Die Attributes „Image16by16“ und „Image32by32“ zeigen auf Resourcen (in diesem Fall Icons), welche schon vorweg in der Solution sein müssen. Habt Ihr noch keine hinzugefügt, könnt Ihr diese Attributes vorerst auch leer lassen.

Nun sehen wir uns die „“-Sektion genauer an und fügen eine CommandDefinition hinzu:

[sourcecode language=“xml“]
<CommandDefinitions>
<CommandDefinition <strong>Id="Mscrm.Isv.wit_schnellerfassung.Form.Group0.Control0"></strong>
<EnableRules>
<EnableRule Id="schnellerfassungEnableDisable"></EnableRule>
</EnableRules>
<DisplayRules />
<Actions>
<JavaScriptFunction Library="$webresource:wit_schnellerfassung" FunctionName="Generate" />
</Actions>
</CommandDefinition>
</CommandDefinitions>
[/sourcecode]

Die fett markierte ID der CommandDefinition ist ident mit dem Command-Attribut von vorhin!! Achtet darauf, dass dies auch bei euch ident ist.
Unter EnableRules fügen wir nun eine Rule hinzu. Auch hier wird die ID später noch benötigt!! Unter Actions fügen wir eine Javascript Action hinzu. In diesem Fall soll bei Klick auf den Button die Funktion „GenerateName“ aus der WebResource „wit_schnellerfassung“ aufgerufen werden.

Und zu guter letzt muss noch die -Sektion angepasst werden:

[sourcecode language=“xml“]
<EnableRules>
<EnableRule Id="schnellerfassungEnableDisable">
<CustomRule FunctionName="ShowHideGenerateButton" Library="$webresource:wit_schnellerfassung"></CustomRule>
</EnableRule>
</EnableRules>
</RuleDefinitions>
[/sourcecode]

Die ID der EnableRule muss mit jener von vorhin übereinstimmen! Die CustomRule ruft wieder eine Javascript-Funktion aus der Resource „wit_schnellerfassung“ auf, welche true/false retournieren muss.

In meinem Fall sieht diese Funktion (noch) wie folgt aus:

[sourcecode language=“javascript“]
function ShowHideGenerateButton() {
var entityId = Xrm.Page.data.entity.getId();
if (!Utils.StringIsNullOrEmpty(entityId)) {
return true;
}
else {
return false;
}
}
[/sourcecode]

Viel Spaß beim Nachmachen.

Happy Programming!

CRM 2011 – Customizations.xml – Validieren!

Christian Wagnsonner 1 Comment

Ein Problem, das vielen vermutlich schon einige Produktivstunden gekostet hat, ist wohl das Erweitern bzw. Validieren der Solution-Customizations.xml-Datei. Wenn man da mal eine Menge an angepassten Entitäten drin hat, wird es schnell unübersichtlich und das Anpassen eine Tortur.

Am einfachsten hilft man sich, in dem man die Schema-Dateien (Teil der SDK) herunterlädt und im Visual Studio einbindet. Im Installations-Ordner der SDK befindet sich ein Ordner „schemas“. Öffnet nun einfach euer Visual Studio und bindet die Schema-Dateien mittels folgenden Schritten ein:

* XML –>
* Schemas… –>
* Add…

Und weil es mich gerade einiges an Zeit gekostet hat: Achtet auf die Sensitivity. Id != id.

Happy Programming!

CRM 2011 – Custom View/Lookup aufbauen und filtern

Christian Wagnsonner 4 Comments

Wenn man etwas mit CRM arbeitet, stößt man zwangsläufig auf einige Limitierungen. Einfaches Beispiel: Man hat eine Verkaufschance und darunter eine Entität, z.B. Produkt (1:n). Dieses Produkt hat jedoch eine weitere Entität darunter, z.B. Produktpositionen (wieder 1:n). Auf der Form für Produktpositionen möchte ich jetzt als Lookup das Produkt auswählen. CRM listet in diesem Fall nun sämtliche Produkte auf und nicht nur jene, die unter der Verkaufschance hängen.

In so einem Fall kann man einen eigenen View (Stichwort CustomView) zur Laufzeit erstellen und dem Lookup zuordnen.
Hierfür habe ich mir eine Helper-Funktion geschrieben, welcher man u.a. die dynamisch die Spalten des Lookups erstellt, Filter/Ordering setzt und die Auswahl eines anderen Views im Lookup unterbindet.

[sourcecode language=“javascript“]

function SetViewForProjektProdukt() {
var lookupEntity = "lookupentity"; // Name der Entität, die im Lookup angezeigt werden soll
var lookupEntityPK = "lookupEntityPK "; // PrimaryKey der Entität, die im Lookup angezeigt werden soll
var lookupFieldId = "lookupFieldId "; // ID des Lookups im Formular, für das der CustomView eingerichtet werden soll
var lookupName = "lookupName"; // Bezeichnung des Lookups

var lookupFormColumns = new Array(); // Spalten welche im Lookup angezeigt werden sollen, müssen Teil der Entität "lookupentity" sein
lookupFormColumns[0] = "new_attr1";
lookupFormColumns[1] = "new_attr2";

var filterAttribute = "new_attr1";
var filterValue = ‚{GUID}‘;

var orderAttribute = "createdon"; // Feld, nach dem sortiert werden soll
var orderDescending = "true"; // asc/desc

SetLookupForm(lookupFieldId, lookupEntity, lookupEntityPK, lookupFormColumns, lookupName, filterAttribute, filterValue, orderAttribute, orderDescending);
}

function SetLookupForm(lookupFieldId, lookupEntity, lookupEntityPK, lookupFormColumns, lookupName, filterAttribute, filterValue, orderAttribute, orderDescending) {

if (Utils.StringIsNullOrEmpty(orderAttribute)) {
orderAttribute = "createdon";
}

if (Utils.StringIsNullOrEmpty(orderDescending)) {
orderDescending = "false";
}

//Note: in the form designer make sure the lookup field is set to "Show all views" in its "View Selector" property
//Set parameters values needed for the creation of a new lookup view…
var viewId = Xrm.Page.context.getUserId(); // Your new lookup views needs a unique id. It must be a GUID. Here I use the GUID of the current user id.
var entityName = lookupEntity; // The entity your new lookup view relates to
var viewDisplayName = lookupName; // A name for new lookup view
var viewIsDefault = true; // Whether your new lookup view should be the default view displayed in the lookup or not

//Define the Fetch XML query your new lookup view will use. You can create this via Advanced Find. You’ll need to massage the syntax a little though
var fetchXml =
"<fetch version=’1.0′ output-format=’xml-platform‘ mapping=’logical‘ distinct=’false‘>" +
"<entity name=’" + lookupEntity + "‘>";

for (var i = 0; i < lookupFormColumns.length; i++) {
fetchXml = fetchXml + "<attribute name=’" + lookupFormColumns[i] + "‘ />";
}

fetchXml = fetchXml +
"<filter type=’and‘>" +
"<condition attribute=’" + filterAttribute + "‘ operator=’eq‘ value=’" + filterValue + "‘ />" +
"</filter>" +
"<order attribute=’" + orderAttribute + "‘ decending=’" + orderDescending + "‘ />" +
"</entity>" +
"</fetch>";

//Define the appearance of your new lookup view
var layoutXml =
"<grid name=’resultset‘ object=’1′ jump=’name‘ select=’1′ icon=’1′ preview=’1′>" +
"<row name=’result‘ id=’" + lookupEntityPK + "‘>"; // id = the GUID field from your view that the lookup should return to the CRM form

for (var i = 0; i < lookupFormColumns.length; i++) {
layoutXml = layoutXml + "<cell name=’" + lookupFormColumns[i] + "‘ width=’100′ />";
}

layoutXml = layoutXml +
"</row>" +
"</grid>";

//Add your new view to the Lookup’s set of availabe views and make it the default view
$("#" + lookupFieldId).attr("disableViewPicker", "0");
Xrm.Page.getControl(lookupFieldId).addCustomView(viewId, entityName, viewDisplayName, fetchXml, layoutXml, viewIsDefault);
$("#" + lookupFieldId).attr("disableViewPicker", "1");

}
[/sourcecode]

Ich hoffe, ich kann euch damit ein paar Stunden eurer kostbaren Zeit sparen 😉

Cheers,
Chris

CRM 2011 – Metadaten auslesen (z.B. von Picklist)

CRM hat die Angewohnheit, dass es nicht den selektierten Wert (z.B. in einer Picklist) speichert, sondern den dahinterliegenden (internen) Code. Das mag durchaus Sinn machen, erschwert einem aber die Arbeit, wenn man programmatisch etwas bearbeiten möchte

Nachfolgend eine Funktion, welche die Metadaten entsprechend umwandelt, also z.B. selektierter Text in die interne Nummer.

[sourcecode language=“csharp“]
/// <summary>
/// Gets the metadata value.
/// </summary>
/// <param name="organization">The organization.</param>
/// <param name="entity">The entity.</param>
/// <param name="attribute">The attribute.</param>
/// <returns></returns>
public static string GetMetadataValue(contracts.IOrganization organization,Entity entity, string attribute)
{

IOrganizationService _service = OrgService(organization);
RetrieveAttributeRequest attributeRequest = new RetrieveAttributeRequest
{
EntityLogicalName = entity.LogicalName,
LogicalName = attribute,
RetrieveAsIfPublished = true
};

RetrieveAttributeResponse attMetadata = (RetrieveAttributeResponse)_service.Execute(attributeRequest);
object oAttribute = entity[attribute];

string sReturn = string.Empty;
if (oAttribute.GetType().Equals(typeof(Microsoft.Xrm.Sdk.OptionSetValue)))
{

OptionMetadata[] optionList = null;
// Access the retrieved attribute.
//’Microsoft.Xrm.Sdk.Metadata.PicklistAttributeMetadata‘
if (attMetadata.AttributeMetadata.GetType().FullName.Contains("PicklistAttributeMetadata"))
{
PicklistAttributeMetadata retrievedPicklistAttributeMetadata =
(PicklistAttributeMetadata)attMetadata.AttributeMetadata;
// Get the current options list for the retrieved attribute.
optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray();
}
else if (attMetadata.AttributeMetadata.GetType().FullName.Contains("StatusAttributeMetadata"))
{
StatusAttributeMetadata retrievedPicklistAttributeMetadata =
(StatusAttributeMetadata)attMetadata.AttributeMetadata;
// Get the current options list for the retrieved attribute.
optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray();
}
else if (attMetadata.AttributeMetadata.GetType().FullName.Contains("StateAttributeMetadata"))
{
StateAttributeMetadata retrievedPicklistAttributeMetadata =
(StateAttributeMetadata)attMetadata.AttributeMetadata;
// Get the current options list for the retrieved attribute.
optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray();
}
else
return string.Empty;
// get the text values
int i = int.Parse((oAttribute as Microsoft.Xrm.Sdk.OptionSetValue).Value.ToString());
for (int c = 0; c < optionList.Length; c++)
{
OptionMetadata opmetadata = (OptionMetadata)optionList.GetValue(c);
if (opmetadata.Value == i)
{
sReturn = opmetadata.Label.UserLocalizedLabel.Label;
break;
}
}

}
else if (oAttribute.GetType().Equals(typeof(Microsoft.Xrm.Sdk.Money)))
{
sReturn = (oAttribute as Microsoft.Xrm.Sdk.Money).Value.ToString();
}
else if (oAttribute.GetType().Equals(typeof(Microsoft.Xrm.Sdk.EntityReference)))
{
sReturn = (oAttribute as Microsoft.Xrm.Sdk.EntityReference).Name;
}
else
{
sReturn = oAttribute.ToString();
}
if (sReturn == null || sReturn.Length == 0)
sReturn = "No Value";
return sReturn;
}

[/sourcecode]

Ich hoffe, ich konnte euch damit helfen 🙂

Cheers,
Chris

Einfacher Import in CRM 2011 von SQL Datenbank (ADO.NET Entity Framework)

Heute hat mich ein typisches Migrationsprojekt erreicht. Eine Marketinganwendung des Kunden soll durch CRM 2011. Dafür ist es zunächst notwendig, die Daten in CRM zu importieren. Erst danach sollen eventuell fehlende Features, etc. besprochen/entwickelt werden.

Heute zeige ich euch, wie man mittels den von CRM zur Verfügung gestellten Webservice-Schnittstellen Firmen aus einer SQL-Datenbank möglichst einfach in CRM importieren kann.

Um den Zugriff auf den SQL-Server so einfach wie möglich zu halten, habe ich mich dafür entschieden auf ADO.NET Entity Data Model zu setzen und habe dem Model sämtliche Tabellen der Quell-Datenbank hinzugefügt.

Nun lässt sich im C# Code ganz einfach auf die Datenbank zugreifen, z.B.

[sourcecode language=“csharp“]
conventoEntities context = new conventoEntities();
var firmen = (from p in context.Tbl_Firmen
orderby p.Firmenname1
select p);
[/sourcecode]

Im nächsten Schritt habe ich die notwendigen CRM-Webservices hinzugefügt. Im nachfolgenden Beispiel wird das Discovery und das Organization-Webservice benötigt. Eine genauere Beschreibung der Services findet ihr u.a. hier:

http://technet.microsoft.com/de-ch/library/gg309557(en-us).aspx
http://technet.microsoft.com/de-ch/library/gg328127(en-us).aspx
http://technet.microsoft.com/de-ch/library/microsoft.xrm.sdk.discovery.idiscoveryservice(en-us).aspx
http://technet.microsoft.com/de-ch/library/microsoft.xrm.sdk.iorganizationservice(en-us).aspx

Die Webservices, sind in der Standard-CRM-Installation enthalten, findet Ihr nach folgendem Schema:
http://[servername]:[port]/[Organisation]/XRMServices/2011/Organization.svc
http://[servername]:[port]/[Organisation]/XRMServices/2011/Discovery.svc

Wie man eine WebReferenz hinzufügt, habe ich in einem meiner letzten Blogs beschrieben.

Bevor wir zum eigentlichen Import-Code kommen, sei erwähnt, dass ich die Late-Binding-Variante verwendet habe. Man kann auch das sog. Early-Binding verwenden, das ist jedoch nicht Scope dieses Blog-Eintrags. Eventuell widme ich dem Thema noch später im Laufe des Projekts. Falls das so ist, werde ich diesen Artikel aktualisieren oder erweitern.

Nachfolgend nun das eigentliche Code-Snippet, welches folgende Aufgaben erledigt:

  1. Auslesen sämtlicher Firmen aus der Quell-Datenbank (mittels ADO.NET Entity Framework)
  2. Loopen durch alle Firmen
  3. CRM-Entitäts-Record erzeugen (Account/Firma)
  4. Eigenschaften mappen
  5. Einfacher Dublettencheck
  6. Einfügen / Aktualisieren

[sourcecode language=“csharp“]
static void Main(string[] args)
{
xxxEntities context = new xxxEntities();
var firmen = (from p in context.Tbl_Firmen
orderby p.Firmenname1
select p);

foreach(var firma in firmen)
{
Guid pk;
bool insert = InsertOrUpdate(firma.Firmenname1, out pk);

Entity account = new Entity("account");
account["name"] = firma.Firmenname1;
account["address1_line1"] = firma.Strasse;
account["address1_postalcode"] = firma.PLZ;
account["address1_city"] = firma.Ort;
account["telephone1"] = firma.Telefon;

account["telephone2"] = firma.Telefon1;
account["fax"] = firma.Telefax;
account["emailaddress1"] = firma.EMail;
account["emailaddress2"] = firma.EMail1;
//account["industrycode"] = firma.Branche1;
account["address1_county"] = firma.Bundesland;
account["address1_country"] = firma.Land;
//account["revenue"] = firma.Umsatz_Tsd;

if (insert)
Helper.OrgService.Create(account);
else
{
account["accountid"] = pk;
Helper.OrgService.Update(account);
}
}
}

private static bool InsertOrUpdate(string accountName, out Guid pk)
{
pk = new Guid();
var accounts = (from a in Helper.OrgContext.CreateQuery("account")
where (string) a["name"] == accountName
select a).FirstOrDefault();

if (accounts != null)
pk = new Guid(accounts["accountid"].ToString());

return pk == new Guid();
}
[/sourcecode]

Nachfolgend noch der Source meiner kleinen Helper-Klasse, welcher nur zwei Properties zum Arbeiten mit dem Organization-Webservice beinhaltet:

[sourcecode language=“csharp“]
public static class Helper
{
private static IOrganizationService _orgService;
public static IOrganizationService OrgService
{
get
{
if(_orgService == null)
{
ServerConnection serverConnect = new ServerConnection();
ServerConnection.Configuration config = serverConnect.GetServerConfiguration();
OrganizationServiceProxy proxy = new OrganizationServiceProxy(config.OrganizationUri, config.HomeRealmUri, config.Credentials, config.DeviceCredentials);

proxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
_orgService = (IOrganizationService)proxy;
}
return _orgService;
}
}

private static OrganizationServiceContext _orgContext;
public static OrganizationServiceContext OrgContext
{
get
{
if(_orgContext == null)
{
_orgContext = new OrganizationServiceContext(OrgService);
}
return _orgContext;
}
}

}
[/sourcecode]

Viel Spaß beim Probieren!