Mir stellten sich folgende Fragen:
Zur Nutzung des neuen HTML Editor benötigt man lediglich eine Textvariable und den ExtendedDataType = “RichContent”.
So sieht der HTML Editor dann aus. Es ist zu berücksichtigen, dass ein HTML Feld mit dem ExtendedDatatype = RichContent, das einzige Feld in der Gruppe sein soll, damit die Seite korrekt gerendert werden.
Dieser HTML Text hat keine Zeichenbegrenzung, daher empfiehlt es sich eine nicht begrenzte Textvariable zu nutzen. Muss der Text in der Datenbank gespeichert werden, dann kann dies in einem Blob gespeichert werden. Dafür können zum Lesen/Schreiben InStream und OutStream verwendet werden.
Speichern in ein Blob Feld:
Lesen aus einem Blob:
Schauen wir uns den HTML Text an, dann kann man sehen, dass dort das HTML Tag <div> für eine einzelne Zeile genutzt wird:
Damit kann der Text in einzelne Teiltexte getrennt werden. Dafür kann beispielsweise folgende Funktion genutzt werden, die dann den HTML Text in eine Liste überträgt:
Per Aktion können wir die Funktion nutzen um die Anzahl der Zeilen auszugeben:
In unserem Beispiel haben wir 4 Zeilen:
Eine neue Zeile wird nur bei Enter gebildet.
Die Ausgabe eines HTML Text in RDLC kann per Platzhalter ausgegeben werden.
Es werden allerdings nur die folgenden Tags in RDLC unterstützt:
Das bedeutet, dass Farben oder auch Bilder in einem Ausdruck nicht dargestellt werden. Außerdem sollte berücksichtigt werden, dass die Übergabe eines langen HTML Textes an eine Textbox mit aktivierter HTML Interpretierung auch zusammengehalten wird. Überschreitet der Text den auf einer Seite zur Verfügung stehenden Seite, dann wird dieser einfach abgeschnitten. Daher kann die oben gezeigte Funktion mit der Liste genutzt werden um eine zeilenbasierte Ausgabe zu entwickeln. Dadurch wird das Rendering und die Ausgabe verbessert und der Platz besser genutzt.
Testweise habe ich dazu einen Beleg angelegt. Dieser druckt die HTML Texte pro HTML Kopf und druckt die HTML Texte zeilenweise aus:
Im Layout wird dann eine Tabelle mit der zeilenbasierten Texten ausgegeben und der Platzhalter besitzt den Markuptyp “HTML – HTML Tags als Formate interpretieren”:
Im HTML Editor werden einige Zeilen erfasst mit unterschiedlichen Formatierungen:
Im Druck sieht, dass Ganze so aus:
Die Ränder wurden auf Schwarz gesetzt, damit man sehen kann, dass die Texte aufgeteilt in einzelnen Textboxen gedruckt werden. Außerdem kann man sehen, dass das Bild nicht gedruckt wurde und einige HTML Tags nicht interpretiert werden (beispielsweise Hintergrundfarbe / Schriftfarbe). Demnach sind nicht unterstützte HTML Tags grundsätzlich kein Problem, da diese ignoriert werden.
Zusätzlich gibt es die Möglichkeit Bilder aus dem HTML Text zu extrahieren und den Base64 Code an den RDLC Beleg zu übergeben. Hier kann dann ein Bildelement im RDLC genutzt werden um das Bild auszugeben. Dabei ist zu berücksichtigen, dass Bilder nur in separaten Zeilen ausgegeben werden können und nicht nebeneinander. Auch ist die Größe der Bilder nicht so einfach anpassbar. Bilder in RDLC sind statisch und können damit nicht “flexibel” groß sein.
Bilder können über den HTML Tag <img src=*> erkannt werden.
Allerdings ist es möglich über die Codeunit Image die Höhe und die Breite eines Bilder zu ermitteln. Mit diesen Informationen können in einer RDLC Tabelle mehrere Zeilen mit unterschiedlichen Höhen angegeben werden (Pixelhöhe 50, 100, 200, 300, 400, 500, …). Das Bild wird dennoch etwas skaliert, da die Höhe des Bildes nicht generisch wachsen oder schrumpfen kann. Dennoch ist es so möglich, dass ein Output die Bilder nahezu in der ursprünglichen Größe ausgegeben werden ohne leeren Platz im Druck zu erzeugen.
Hier gibt es ein Beispiel um die Größe des ersten Bildes des HTML Textes zu ermitteln:
Um eine Ausgabe eines Bildes im Bericht zu implementieren, wird eine Funktion benötigt, die die Bilder in einem HTML Text erkennt, extrahiert und separat an das RDLC Layout übergibt.
So könnte das aussehen:
Dies erstellt eine Liste mit den Base64 Codes der Bilder zu einem HTML Text. Im Bericht kann dann ein weiteres Integer DataItem eingefügt werden, welches die Bilder für eine einzelne HTML Zeile ermittelt und diese dann zusammen mit der Bildhöhe an RDLC übergibt.
Es ist zu beachten, dass diese Logik ihre Grenzen hat, aber für einige Anwendungsfälle evtl. nutzbar. Im RDLC gibt es dafür einfach mehrere Zeilen in der Tabelle mit unterschiedlich großen Bildern und das Bild wird dann in der Zeile gerendert, die ungefähr die gleiche Größe hat. Alle anderen Zeilen werden ausgeblendet:
Im Programmcode selbst muss nur geprüft werden, welche Höhe das Bild hat und basierend darauf die richtige Zeile gedruckt werden. Ich sehe sonst keinen anderen Weg Bilder aus dem HTML Editor ansatzweise in der richtigen Größe in RDLC zu drucken.
Persönliche Meinung
Der HTML Editor an sich ist top. Dies ohne Addin im Webclient nutzen zu können ist wirklich gut. Die Integration in Belege ist aber noch nicht vollständig möglich. Es gibt dazu bereits unterschiedlichste Anfragen in den BC Ideas. Wenn mehr HTML Tags im RDLC unterstützt werden, könnte diese Funktionalität tatsächlich interessant werden. Ansonsten sollte auf die Integration der HTML Texte im RDLC verzichtet oder nur in abgegrenzten Fällen genutzt werden.
The post Business Central HTML (RichContent) & Belegausgabe in RDLC first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>wie kann eine PostgreSQL Datenbank in Dynamics NAV / Dynamics Business Central angebunden werden? Genau vor dieser Frage standen meine Kollegen und ich in einem Projekt.
Für mich lagen drei Lösungsansätze auf der Hand:
Auf Option 3 gehe ich in diesem Blog nicht ein, da es zum Thema REST mit Dynamics NAV / BC eigentlich schon genug Informationen gibt.
Für Option 1 und 2 wird als Basis ein separater Treiber benötigt. Microsoft SQL Server kann nicht direkt ohne Treiber mit einer PostgreSQL Datenbank verbunden werden. Ich habe mich für diesen Treiber entschieden: https://odbc.postgresql.org/
Ich empfehle die 64-bit Variante des Treibers. Die Installation des Treibers sollte als Administrator ausgeführt werden. Die Installation sollte dort installiert werden, wo dieser später auch benutzt wird. Beispiele:
Die Installation ist einfach durchzuführen. Starte die Installation und klicke auf “Weiter” (Next):
Akzeptiere nach dem Lesen die “License Agreement”:
Anschließend können die Module ausgewählt werden. Die Dokumentation ist optional:
Die Installation wird anschließend ausgeführt und benötigt Adminrechte.
Nach der Installation können wir die Verbindung testen. Dafür können wir das Feature “System DNS” von Windows Server nutzen. Dazu muss auf dem Windows Server der “Server Manager” geöffnet werden.
Im Server Manager können wir über Tools die Option “ODBC Data Sources (64-bit)” auswählen:
In dem neuen Fenster wählen wir den Reiter System DSN aus.
Hier können wir nun einen neuen Eintrag hinzufügen. Dazu klicken wir auf “Add” (Hinzufügen). Wir wählen anschließend den Treiber “PostgreSQL Unicode (x64)” aus:
Die folgenden Werte sollten ausgefüllt werden:
Anschließend sollten die Einstellungen gespeichert werden. Der Treiber kann innerhalb der Option “Datasource” / “Global” noch eingestellt werden. Dort werden auch immer wieder Optionen verbessert. Es lohnt sich daher von Zeit zu Zeit auch den Treiber zu aktualisieren, falls ein bestimmtes Problem besteht. Beispielsweise kann über den Treiber gesteuert werden, ob boolean Felder als integer (mit 0 oder 1) oder als Chars übertragen werden.
Bei Probleme mit dem Treiber lohnt sich ein Blick ins Wiki oder in dafür spezialisierten Foren (beispielsweise: https://www.pg-forum.de)
Nach der Einstellung der Verbindungsparameter kann der Treiber mit der Aktion “Test” geprüft werden:
Sobald die Verbindung mit System DNS funktioniert, sollte auch die Verbindung mit dem SQL Server oder mit Dynamics NAV funktionieren. Das ist die bereits die halbe Miete. Sollte die Verbindung nicht klappen, sollte generell geprüft werden, ob der PostgreSQL Server erreichbar ist. Wird dieser in der Cloud gehostet sind ggf. IP Freigaben notwendig um überhaupt eine Verbindung aufbauen zu können.
Empfehlenswert ist ebenfalls ein Tool um auf die PostgreSQL Datenbank zugreifen zu können. Dafür habe ich pgadmin verwendet, da es übersichtlich und einfach ist.
Hinweis: Ich hatte Probleme mit pgAdmin in der Verwendung des Internet Explorers. Vermutlich müssen einige Skripte in den Internet Einstellung aktiviert werden. Mit anderen Browsern klappt es ohne Probleme.
Im SQL Server Management Studio können wir eine Verbindung per “Linked Server” aufbauen.
Dazu erstellen wir einen neuen Linked Server und wählen die folgenden Einstellungen:
Unter Sicherheit/Security sollte die Option “be made using this security context” ausgewählt werden. Benutzername und Password müssen hier erneut eingegeben werden.
Nach der Einrichtung des Linked Servers kann per Rechtsklick “Test Connection” ausgewählt werden. Die Verbindung sollte auch hier erfolgreich sein. Auch sollte man unter Catalogs die PostgreSQL Datenbank sehen und unter Tables die einzelnen PostgreSQL Tabellen. Es gibt auch die Möglichkeit per Connection String den SQL Server mit der PostgreSQL Datenbank zu verbinden. In diesem Fall ist keine Konfiguration in den System DNS Einstellungen notwendig.
Zum Lesen der PostgreSQL Tabellen wird immer ein Skript benötigt.
Beispiel:
SELECT number ISNULL(name), 100), '') as name, ISNULL(email), 80), '') as email, ISNULL(CAST(active_customer as tinyint), 0) as active_customer, CONVERT(VARCHAR,ISNULL(updated_at, CAST('1753-01-01 00:00:00:000' as datetime2))) as updated_at FROM OPENQUERY(NAVLinkedServer, 'SELECT number, name::varchar(255), email::varchar(1000), active_customer, updated_at FROM public.customers'); GO
Wir benötigen den Befehl OPENQUERY, da wir mit einem normalen Skript nicht alle Datentypen korrekt lesen können und höchstwahrscheinlich Probleme bekommen. Allerdings hat OPENQUERY auch den Nachteil, das das SELECT Statement nur statisch mit einer WHERE Klausel eingeschränkt werden kann. Erstellen wir hieraus eine View und verknüpfen diese mit einer Tabelle in NAV werden wir immer das gleiche SELECT Statement verwenden um aus der PostgreSQL Tabelle Daten zu lesen. Sollte man mit Tabellen arbeiten, die immer mehr Daten beinhalten, dann ist die Option möglicherweise nicht sinnvoll.
Außerdem muss hier der Transaction Type in NAV geändert werden. Der Standard ist ReadNoLocks. Das wird aber dazu führen, das in dem SQL SELECT ein “BEGIN DISTRIBUTED TRAN” ergänzt wird. Das wird von PostgreSQL nicht unterstützt. Demnach muss der TransactionType auf “Browse” umgestellt werden, wenn Daten von PostgreSQL gelesen werden sollen.
Mehr dazu kann in der Online Hilfe lesen: https://docs.microsoft.com/en-us/dynamics-nav/currenttransactiontype-function–database-
Die Integration innerhalb des SQL Servers ist also relativ unflexibel. Hier benötigt man allerdings keine Programmierung mit .NET in Dynamics NAV. Es reicht die Erstellung einer View in der NAV Datenbank, das Verknüpfen der View per LinkedObject in NAV und das Umstellen des Transaction Type in NAV.
Für den Zugriff auf die PostgreSQL Datenbank benötigen .NET Variablen. Das sind die folgenden:
Als Text:
ODBCConnection – DotNet – System.Data.Odbc.OdbcConnection.’System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
ODBCCommand – DotNet – System.Data.Odbc.OdbcCommand.’System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
ODBCDataReader – DotNet – System.Data.Odbc.OdbcDataReader.’System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
Folgende Hinweise:
So könnte die Funktion zum Holen von Daten dann aussehen. Die Variable QueryText ist eine Text Variable und beinhaltet das SELECT Statement. Was ihr hier braucht hängt stark von euren Anforderungen ab. Ihr könnt eure SELECT Abfrage aber auch vorher in pgAdmin testen und dann in NAV übernehmen. Denkt daran bei großen Tabellen eure Abfrage mit einer WHERE Klausel korrekt einzuschränken. Hier bieten sich unterschiedliche Konzepte an:
Es gibt auch andere Varianten, aber ihr solltet diese Thematik in jeden Fall berücksichtigen.
Wurde die Funktion korrekt ausgeführt, dann könnt ihr mit “IF ODBCDataReader.HasRows THEN” prüfen, ob ihr Daten für eure Abfrage erhalten habt. Falls das nicht der Fall ist, könnt ihr damit die Durchführung von unnötigen Code vermeiden und die Codeausführung stoppen.
Mit dem Befehl “WHILE ODBCDataReader.Read DO BEGIN” könnt ihr durch jeden Eintrag eures Ergebnisses durchgehen. Die Schleife arbeitet die Daten in der korrekten Reihenfolge ab. Diese kann in dem SELECT Statement auch mit “ORDER BY” beeinflusst werden.
Zusätzlich gibt es dann diverse Befehle um die einzelnen Spalten korrekt zu lesen:
Bei decimal Feldern muss ggf wie folgt vorgegangen werden:
Am Ende des Lesevorgangs sollte die Verbindung ordnungsgemäß geschlossen werden:
Ich hatte bei der Integration in NAV immer ausfüllende Leerzeichen erhalten. Anstatt “Gustav” stand beispielsweise “Gustav ” im Namen. Diese Leerzeichen habe ich per Funktion abgeschnitten:
Ich halte die .NET Integration für sehr flexibel und einfach erweiterbar. Ein Wechsel des TransactionsType ist hier nicht notwendig. Die .NET Variante wird vermutlich auch noch sehr lange nutzbar sein, sofern man die Service Tier selber betreibt. In der Microsoft Cloud ohne Zugriff auf die Server sollte man sich eher mit der REST api in AL befassen und die Daten per Web Service austauschen. Wer allerdings noch länger plant OnPremise auf einem lokalen oder einem eigenen Cloud Server zu nutzen, kann die .NET Integration mit Ausführung auf dem Server noch eine ganze Weile nutzen. Dies sollte problemlos mit C/AL oder AL funktionieren.
Ich hoffe das dieser Blog beim Einstieg in dieses Thema hilft.
Viel Spaß.
The post Dynamics NAV / Business Central mit einer PostgreSQL Datenbank verbinden first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>in Gesprächen mit Kollegen und Freunden im Dynamics NAV / Business Central Bereich stelle ich immer wieder fest, dass viele noch nie etwas von FILTERGROUP(-1) gehört haben. Dabei ist das sogar auf der Microsoft Online Dokumentation beschrieben (FILTERGROUP Function (Record)).
Schauen wir uns die Dokumentation einmal an:
Dabei werden die Dynamics NAV Experten sicherlich schon wissen worum es sich hier handelt. Mit “Cross-column” kann über mehrere Felder gleichzeitig gesucht werden. Ich erkläre das gerne an einem Beispiel. Stellen wir uns vor, dass wir in der Kontakttabelle nach dem Suchwort “GmbH” suchen wollen. Dabei stellt sich die Herausforderung, dass manche Kontakte einen langen Namen haben und “GmbH” nicht im Feld “Name”, sondern im Feld “Name 2” steht.
Wie lösen wir dieses Problem? Dabei wird oft zur der Lösung gegriffen, die entsprechende Tabelle zwei Mal zu durchsuchen:
Das Ergebnis geben wir in einer Nachricht aus.
Wir haben also 6 Kontakte, die GmbH im Feld “Name” oder “Name 2” vorweisen können. Das ist natürlich nur ein Beispiel, aber wie könnte diese Problem besser gelöst werden? Wie wäre es mit FILTERGROUP(-1):
Das Ergebnis ist wie im Beispiel vorher:
FILTERGROUP(-1) bietet uns die Möglichkeit Filter in Dynamics NAV / Business Central mit ODER zu verknüpfen. Ohne FILTERGROUP(-1) werden alle Filter mit UND verbunden. Schlußfolgern können wir daraus, dass wir mit FILTERGROUP(-1) Daten innerhalb einer SQL Abfrage ermitteln können und über mehrere Spalten gleichzeitig suchen können.
Natürlich sollte das gut bedacht sein und für den entsprechenden Fall überprüft werden. Ab einer gewissen Datenmenge und ohne entsprechende Indexe muss die Abfrage nicht unbedingt schneller sein. Anwendung könnte FILTERGROUP(-1) beispielsweise in einer Entwicklung finden, die alle oder bestimmte Felder einer Stammdatentabelle nach einem Suchbegriff durchsucht. Das wäre in Verbindung mit FIELDREF möglich. Wir könnten daher mehrere Felder generisch nach einem Suchbegriff durchsuchen und dies dem Benutzer anzeigen. Dies ist nur eine der möglichen Anwendungsbereiche. Bei Prüfungen, ob eins von vielen Felder gefüllt sind, könnte sich der Cross-column search auch anbieten.
Ich bin mir sicher, dass die meisten Entwickler schon Ideen haben, FILTERGROUP für ihre Anforderung zu verwenden.
Viel Spaß.
The post FILTERGROUP(-1) – Cross Column Search in Dynamics NAV / BC first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>heute möchte ich ein erklären, wie ich den Übertrag in RDLC löse. Wer sich vorher nochmal einlesen möchte, kann sich hier die vorherigen Beiträge ansehen:
Folgende Restriktionen sollten vorher bekannt sein:
Damit ihr nachvollziehen könnt, was genau ändere, nutze ich als Basis benutze den Beispielbeleg aus meinem letzten Beitrag zum Thema RDLC: 160312_RobertsVKRechnung
Diesen Bericht habe ich in eine Dynamics NAV 2018 Datenbank eingespielt und dann in Visual Studio 2017 (Ansicht + Layout) geöffnet. Dafür benötigt ihr die Visual Studio 2017 Professional Version oder Visual Studio 2017 Community Edition (https://www.visualstudio.com/de/downloads/). Bitte informiert euch über die Lizenzbedingung der Community Edition, bevor ihr diese benutzt. Zusätzlich benötigt ihr die Erweiterungen “Microsoft Rdlc Report Designer for Visual Studio” (https://marketplace.visualstudio.com/items?itemName=ProBITools.MicrosoftRdlcReportDesignerforVisualStudio-18001). Nach einen Neustart des Computers, solltet ihr RDLC Belege wieder ordnungsgemäß mit Visual Studio öffnen können. Ohne die Erweiterung kann Visual Studio das Layout nicht laden und ihr bekommt nur eine XML angezeigt.
So sieht das Layout aus, mit dem wir starten:
Bevor wir beginnen, sollten wir zuerst zusammenfassen, welche Anforderungen wir erfüllen müssen.
Auf die Summenlogik gehe ich hier nicht weiter ein. Falls das von Interesse ist, kann das in den Standardbelegen nachgeschaut werden. Für unser Beispiel und zur Erläuterung füge ich eine einfache Summenzeile in unserem Layout ein. Das ist keine Empfehlung, sondern ausschließlich exemplarisch gedacht. Somit sollte die Positionierung von Elementen klar erkennbar und auch auf andere Belege anwendbar sein.
In der Textbox außerhalb der Gruppe und unterhalb des Zeilenbetrags ergänze ich den Ausdruck “=Sum(Fields!LineAmount_Line.Value)”. Die Formatierung ändere ich auf Fett und ergänze in der Textbox links daneben den Text “Summe”. Zusätzlich entferne ich den leeren Platzhalter.
Im heutigen Beispiel buche ich mir eine Verkaufsrechnungen mit genug Zeilen in meine Demodatenbank. Im Druck sieht das nun so aus:
Nun ergänzen wir die Summenlogik. Dafür arbeite ich mit VB Code. Diesen können wir öffnen, indem wir die Berichtseigenschaften öffnen.
Anschließend können wir folgenden Code ergänzen:
Shared RunningTotals As New System.Collections.Hashtable Shared LastPage as boolean Shared TransHeaderValue as boolean Public Function StartPage() as boolean LastPage = false return LastPage End Function Public Function SetLastPage() as boolean LastPage = true return LastPage End Function Public Function GetLastPage() as boolean return LastPage End Function Public Function GetRunningTotal(ByVal CurrentPageNumber) Return IIF(CurrentPageNumber > 0, RunningTotals(CurrentPageNumber), 0) End Function Public Function SetRunningTotal(ByVal CurrentPageTotal, ByVal CurrentPageNumber) RunningTotals(CurrentPageNumber) = CurrentPageTotal + GetRunningTotal(CurrentPageNumber - 1) Return RunningTotals(CurrentPageNumber) End Function Public Function SetTransHeader(ByVal Value) as boolean TransHeaderValue = Value Return TransHeaderValue End Function Public Function GetTransHeader() as boolean Return TransHeaderValue End Function
Damit sind die Vorbereitungen abgeschlossen. Hier eine kurze Erläuterung, wozu die Funktionen dienen:
Die Funktionen müssen nun im Layout aufgerufen werden.
Beginnen wir mit der Funktion “StartPage“. Folgendes wird dafür getan:
Wichtig ist hierbei folgendes:
Als nächstes deklarieren wir das Ende, also ab wenn der Übertrag nicht mehr gedruckt werden soll. Das machen wir mit der Funktion “SetLastPage“. Diese platzieren wir in einer Tabelle nach der Summe.
Dabei muss berücksichtigt werden, dass sich das Element immer direkt hinter der Summe befindet.
Nun benötigen wir noch ein Element für die Funktion “SetTransHeader“, welches beim Rendern der Fußzeile erkennt, ob die letzte Seite des Beleges bereits gedruckt wurde oder nicht. Dieses Element wird nicht ausgeblendet. Damit gibt es nämlich vor allem in älteren Versionen Probleme. Dafür tue ich folgendes:
Damit sind die Vorbereitungen getroffen. Wir benötigen nun die Textboxen für den Übertrag. Beginnen wir im Fuß für die Funktion “SetRunningTotal“:
Nun erstellen wir im Kopf ebenfalls den Bereich für den Übertrag. Da wir zwei Rechtecke haben (eins für Seite 1 und eins für die restlichen Seiten) fügen wir den Übertrag auf dem Rechteck für Seite 2+ (HeaderSecondPage) ein. Damit stellen wir sicher, dass der Übertrag niemals auf der ersten Seite im Kopf gedruckt wird. Enthält euer Berichtslayout nur eine Seite, dann benötigt das Rechteck für den Übertrag im Kopf in der Hidden Eigenschaft auch eine Prüfung auf die Seitennummer (nur drucken, wenn Seite > 1).
Schauen wir uns nun den Druck in der Seitenansicht an:
Solltet ihr mehrere Rechnungen am Stück drucken, so stellt sicher, dass die Seitennummer bei jedem Beleg zurückgesetzt wird. Sofern das sichergestellt ist, ist diese Funktion auch für den Stapeldruck geeignet.
That’s it. Have fun.
Abschließend möchte ich noch sagen, dass es einige Übertragsbeíspiele im Netz gibt. Darunter ist auch eine Option ohne das Element “TransferHeader_Marker” und die entsprechenden Funktionen. Letztlich hängt es auch von der Dynamics NAV Version ab, welche Integration funktioniert. Ich kann dazu nur sagen, dass vor allem in früheren Dynamics NAV Versionen (2009-2013 R2) Probleme mit ausgeblendeten Elementen (mit der Eigenschaft Hidden = True) gab. Meiner Erfahrung nach funktioniert die oben beschriebene Variante in allen Dynamics NAV Versionen (2009 bis 2018) und daher nutze ich diese auch.
Meinen angepassten Bericht findet ihr hier als ZIP (enthält TXT und FOB): 180408_RobertsRechnungÜbertrag
Ich hoffe dies hilft dem ein oder anderem.
The post RDLC für NAV Entwickler #6 – RDLC Belegdesign – Übertrag first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>heute möchte ich meine Erfahrungen zu Dynamics NAV Performance teilen. Dabei gibt es ein paar Dinge die meist schnell eingestellt sind und für verbesserte Leistung sorgen. Performanceprobleme können in jeder Dynamics NAV Datenbank auftreten. Dabei sind auch ältere Tipps dabei. Dies soll einen grundlegenden Überblick über die Einstellungen geben, auf die ich bei Installationen besonders achte.
Diese Werte stehen in der Datei “CustomSettings.config” im Verzeichnis der Service Tier zur Verfügung. Sie können über die Dynamics NAV Administration oder über einen Editor direkt in der Konfigdatei geändert werden. Nach Änderung eines Wertes ist ein Neustart des Service Tier notwendig, damit die Einstellungen auch greifen.
MetadataProviderCacheSize: Diese Option steht seit Dynamics NAV 2009 RTC zur Verfügung und hat einen Standardwert von 150. Hiermit wird angegeben, wie viele Objekte in Cache gespeichert werden. Dieser Wert sollte so gewählt werden, dass alle genutzten Objekte auch im Cache gespeichert werden. Wird der Wert auf 5000 oder höher gesetzt, wird jedes Objekt auch im Cache gespeichert. Es ist zu empfehlen den Wert entsprechend zu erhöhen.
MaxConcurrentCalls: Dieser Wert bestimmt, wie viele gleichzeitige Anfragen von den Clients abgeschickt werden können. Dieser Wert kann ebenfalls erhöht werden. Als Richtwert nehme ich die maximal gleichzeitig angemeldete Benutzeranzahl auf der Service Tier x 2. Arbeiten also max. 20 Benutzer auf der Service Tier, dann verändere ich den Wert auch nicht. Der Wert steht ab NAV 2009 RTC zur Verfügung.
DisableSmartSQL: Ab NAV 2015 steht die Eigenschaft “DisableSmartSQL” zur Verfügung. Diese Option ist standardmäßig nicht aktiviert. Das sollte auch nicht verändert werden. Wenn die Service Tier mit SmartSQL arbeitet, werden Abfragen gebündelt statt einzeln an den SQL Server übertragen.
Beispiel: Wird die Kontaktliste geöffnet und dort ist eine Factbox mit Flowfeldern enthalten, welche Daten aus den Aktivitätenprotokollposten anzeigt, dann schickt NAV ein kombiniertes SELECT für die Kontakttabelle und die Aktivitätenprotokollposten ab. Ist SmartSQL deaktiviert, dann werden die Information einzeln abgefragt.
Dieser Wert sollte dann temporär aktiviert werden, wenn Performanceproblemen auf die Schliche kommen möchte. Ab Dynamics NAV 2017 meldet die Service Tier in der Ereignisanzeige nämlich auch langsame Skripte.
Das sieht beispielsweise so aus:
In diesen Fall dauert die Abfrage auf ein Individualfeld “Work Description” im Verkaufskopf länger als 10 Sekunden. Dabei werden zwar die Filter für die Felder “Document Type” und “No.” nicht angezeigt, aber diese Meldungen in der Ereignisanzeige können Indikatoren auf Probleme in Zusammenhang mit dem Feld sein. Es besteht die Möglichkeit, dass es kein Schlüssel für das Feld “Work Description” gibt. Es gibt aber auch deutlich komplexere Meldung, wie diese hier:
In diesem Fall kann es helfen “DisableSmartSQL” zu aktivieren. Damit kann der langsame Teil besser identifiziert werden, da kein kombiniertes SQL Statement mehr übertragen wird. Sind die Probleme gefunden, sollte “DisableSmartSQL” wieder deaktiviert werden.
Compile and Load Business Application: Die Service Tier speichert Teile der Anwendung bereits im Arbeitsspeicher und verkürzt somit Antwortzeiten nachdem die Service Tier neugestartet wurde. Daher ist meine Empfehlung diesen Option zu aktivieren bzw. aktiviert zu lassen. Diese Option steht ab Dynamics NAV 2016 zur Verfügung.
Details dazu auch hier zu finden: https://msdn.microsoft.com/de-de/library/hh174007(v=nav.90).aspx
Data Cache Size: Dieser Wert gibt an, wie viel Arbeitsspeicher eine Service Tier für das Cachen von Daten verbrauchen darf. Dabei sind die Werte wie folgt aufgeteilt:
9 -> 2 hoch 9 = 512 MB
10 -> 2 hoch 10 = 1.024 MB
11 -> 2 hoch 11 = 2.048 MB
12 -> 2 hoch 12 = 4.096 MB
13 -> 2 hoch 13 = 8.192 MB
14 -> 2 hoch 14 = 16.384 MB
15 -> 2 hoch 15 = 32.768 MB
Diese Eigenschaft wird oft angepriesen, allerdings habe ich hierdurch kaum Performanceverbesserungen gespürt. Diese Einstellung kann man also im Hinterkopf behalten, aber es sollte nicht zu viel erwartet werden.
Am meisten kann man jedoch direkt am SQL Server optimieren. Dafür gibt es folgende Einstellungen
Gruppenrichtlinien: In den Gruppenrichtlinien am SQL Server sollten dir Rechte für den Dienstbenutzer des SQL Servers verändert werden. Zu empfehlen sind die folgenden Rechte:
Die Gruppenrichtlinien erreicht man wie folgt.
– Ausführen: gpedit.msc / Öffnen von “Computerkonfiguration” -> “Windows-Einstellungen” -> “Sicherheitseinstellungen” -> “Lokale Richtlinien” -> “Zuweisen von Benuterrechten”
Arbeitsspeicher: Arbeitsspeicher ist eines der wichtigsten Ressourcen für den SQL Server. Sind Daten erst mal im Cache, dann werden diese auch schnell abgerufen. Dabei achte ich auf zwei Faustregeln.
– Festlegen von maximalen Arbeitsspeicher des SQL Servers: Das vermeidet, dass der SQL Server zu viel Arbeitsspeicher verwendet, sodass der SQL Server mit 99 % RAM-Auslastung den Server lahm legt. Es ist darauf zu achten, dass auch dem System etwas Arbeitsspeicher zur Verfügung steht.
Konstruiertes Beispiel: 16 GB stehen auf dem SQL Server zur Verfügung. Es läuft der SQL Server und ein Echtsystem, Testsystem, sowie ein NAS auf der Maschine. Hier habe ich den SQL Server auf 12 GB gesetzt. Prüft hier einfach, wie viel Kapazitäten ihr habt und justiert das Limit entsprechend (siehe https://technet.microsoft.com/de-de/library/ms191144(v=sql.105).aspx).
– Erhöhung des Arbeitsspeichers: Es kann zu starken Verbesserungen kommen, wenn der Maximalwert vom SQL Server voll genutzt wird (kann per Ressourcenmonitor geprüft werden) und bei gleicher Aktivität Dynamics NAV mal langsam und mal schnell ist. Das kann dann dafür sprechen, das Daten mal im Cache sind und mal nicht. Nach Erhöhung des Arbeitsspeichers sollte ebenfalls das maximale Limit im SQL Server erhöht werden.
Indexanalyse: Mit der Indexanalyse können erhebliche Verbesserungen der Leistung herbeigeführt werden. Dafür gibt es grundsätzlich zwei Skripte:
-> Skript zur Ermittlung fehlender Schlüssel
SELECT CONVERT (DECIMAL (28,1), migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) ) AS improvement_measure, mid.statement AS table_name, mid.equality_columns, mid.inequality_columns, mid.included_columns, migs.avg_user_impact, migs.unique_compiles, migs.user_seeks, migs.user_scans, migs.last_user_seek, migs.last_user_scan, 'CREATE INDEX [missing_index_' + CONVERT(VARCHAR, mig.index_group_handle) + '_' + CONVERT(VARCHAR, mid.index_handle) + '_' + LEFT(PARSENAME(mid.statement, 1), 32) + ']' + ' ON ' + mid.statement + ' (' + ISNULL(mid.equality_columns, '') + CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END + ISNULL(mid.inequality_columns, '') + ')' + ISNULL(' INCLUDE (' + mid.included_columns + ')', '') AS create_index_statement FROM sys.dm_db_missing_index_groups mig INNER JOIN sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle INNER JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle ORDER BY migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) DESC;
Als Ergebnis bekommt man eine Tabelle mit der Auswertung der Statistiken des SQL Servers. Dabei soll die Spalte “improvement_measure” eine grobe Wertung des Einflusses darstellen. Umso höher der Wert umso wahrscheinlicher, dass nach der Anlage des fehlenden Index das System in diesem Bereich schneller ist. Zusätzlich wird die betroffene Tabelle und die betroffenen Felder angezeigt.
Die Spalte user_seeks ist für mich besonders wichtig. Hieran kann ich erkennen, wie oft der SQL Server den Index gebraucht hätte. Einträge mit höheren Werte werden häufiger gebraucht und haben damit auch einen höheren Einfluss. Wenn die Datenbank schon zwei Jahre läuft und es 5 Seeks für einen Index gibt, dann sind diese in der Regel nicht sehr bedeutsam. Da gibt es zwar auch Ausnahmen, auf die ich jetzt nicht weiter eingehe.
In der Spalte “last_user_seek” kann man auch sehen, wann der Index das letzte mal benötigt wurde.
In diesem Beispiel sieht man, dass es einen Eintrag mit ca. 28.000 user_seeks gibt. Diesen Index schaue ich mir dann näher an. Welche Tabelle ist betroffen, welche Felder sind betroffen, welcher Prozess ist davon eventuell betroffen? In diesem Fall ist Bemerkungstabelle betroffen. Als Lösung gibt es nun zwei Möglichkeiten:
Es gibt hier verschiedene Ansätze, wann man etwas wie tun sollte. Pauschal gesagt, kann man hiermit schnell fehlende Indexe anlegen und kann damit auch Performancesteigerungen erreichen. Dennoch ist eine präzise Analyse der “langsamen” Vorgänge besser, als einfach Indexe anzulegen. Die Anlage von Indexen kann im Livebetrieb vorgenommen werden. Es ist aber zu spüren, das heißt die Performance für NAV sinkt, während der SQL Server Indexe aufbaut.
Ein Tipp noch am Rande: Steht im Create Index Statement ein INCLUDE [timestamp] am Ende, dann kann das INCLUDE inkl. der folgenden Felder gelöscht werden. Das sind nämlich alle Felder der Tabelle.
Es ist ratsam diese Prüfungen regelmäßig durchzuführen. Dazu sollte auch geprüft werden, ob angelegte Schlüssel überhaupt positive Effekte erwirken konnten. Dazu gibt es das folgende Skript.
-> Skript zur Ermittlung, ob angelegte Indexe überhaupt verwendet werden
In diesem Skript werden euch alle Indexe angezeigt, die ihr mit dem oben genannten Skript angelegt habt. Ihr könnt hier sehen, wie oft die Indexe tatsächlich genutzt wurden und wie oft der SQL Server diesen Index auch mit aufgebaut hat. Zusätzlich besteht beispielsweise mit der Spalte “tsql_DROP” die Möglichkeit den Index wieder zu löschen (Inhalt des Feldes in eine neue Abfrage kopieren und ausführen). Das macht vor allem dann Sinn, wenn dieser nie verwendet wurde. Dann kostet der Index nur Ressourcen und sollte wieder entfernt werden. Beachtet dabei, dass wenn die Werte in den Spalten “user_seeks”, “user_scans”, “user_lookups”, “user_updates” alle NULL sind, dass es sich hier vermutlich um einen frisch angelegten Schlüssel handelt.
USE [databasename] -- change db name on demand GO SELECT object_name(sysidx.[object_id]) as [object_name], sysidx.[name] as [index_name], idxcol.key_columns, idxcol.included_columns, sysidx.filter_definition, idxusg."user_seeks", idxusg."user_scans", idxusg."user_lookups", idxusg."user_updates", 'IF NOT EXISTS (SELECT TOP 1 NULL FROM sys.indexes (NOLOCK) WHERE object_name([object_id]) = ''' + object_name(sysidx.[object_id]) + ''' AND [name] = ''' + sysidx.[name] + ''') CREATE INDEX [' + sysidx.[name] + '] ON [' + object_name(sysidx.[object_id]) + '] (' + idxcol.key_columns + ')' + CASE WHEN idxcol.included_columns is not null THEN ' INCLUDE (' + idxcol.included_columns + ')' ELSE '' END + CASE WHEN sysidx.filter_definition is not null THEN ' WHERE ' + sysidx.filter_definition + '' ELSE '' END + ' WITH (MAXDOP = 64, ONLINE = OFF, DROP_EXISTING = OFF); ' as [tsql_CREATE], 'IF EXISTS (SELECT TOP 1 NULL FROM sys.indexes (NOLOCK) WHERE object_name([object_id]) = ''' + object_name(sysidx.[object_id]) + ''' AND [name] = ''' + sysidx.[name] + ''') DROP INDEX [' + sysidx.[name] + '] ON [' + object_name(sysidx.[object_id]) + ']; ' as [tsql_DROP] FROM sys.indexes sysidx LEFT OUTER JOIN sys.dm_db_index_usage_stats idxusg ON sysidx."object_id" = idxusg."object_id" AND sysidx."index_id" = idxusg."index_id" JOIN ( SELECT Tab.[name] AS TableName, Ind.[name] AS IndexName, SUBSTRING (( SELECT ', [' + AC.name + ']' FROM sys.[tables] AS T INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] AND I.[index_id] = IC.[index_id] INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id] WHERE Ind.[object_id] = I.[object_id] AND Ind.index_id = I.index_id AND IC.is_included_column = 0 ORDER BY IC.key_ordinal FOR XML PATH ('')) , 3, 8000) AS key_columns, SUBSTRING (( SELECT ', [' + AC.name + ']' FROM sys.[tables] AS T INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] AND I.[index_id] = IC.[index_id] INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id] WHERE Ind.[object_id] = I.[object_id] AND Ind.index_id = I.index_id AND IC.is_included_column = 1 ORDER BY IC.key_ordinal FOR XML PATH ('')) , 3, 8000) AS included_columns FROM sys.[indexes] Ind INNER JOIN sys.[tables] AS Tab ON Tab.[object_id] = Ind.[object_id] INNER JOIN sys.[schemas] AS Sch ON Sch.[schema_id] = Tab.[schema_id] UNION SELECT Tab.[name] AS TableName, Ind.[name] AS IndexName, SUBSTRING (( SELECT ', [' + AC.name + ']' FROM sys.[views] AS T INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] AND I.[index_id] = IC.[index_id] INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id] WHERE Ind.[object_id] = I.[object_id] AND Ind.index_id = I.index_id AND IC.is_included_column = 0 ORDER BY IC.key_ordinal FOR XML PATH ('')) , 3, 8000) AS key_columns, SUBSTRING (( SELECT ', [' + AC.name + ']' FROM sys.[views] AS T INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] AND I.[index_id] = IC.[index_id] INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id] WHERE Ind.[object_id] = I.[object_id] AND Ind.index_id = I.index_id AND IC.is_included_column = 1 ORDER BY IC.key_ordinal FOR XML PATH ('')) , 3, 8000) AS included_columns FROM sys.[indexes] Ind INNER JOIN sys.[views] AS Tab ON Tab.[object_id] = Ind.[object_id] INNER JOIN sys.[schemas] AS Sch ON Sch.[schema_id] = Tab.[schema_id] ) idxcol ON object_name(sysidx.[object_id]) = idxcol.TableName AND sysidx.[name] = idxcol.IndexName WHERE object_name(sysidx.[object_id]) not like 'ssi%' AND sysidx.[name] like 'ssi%' or sysidx.[name] like 'missing_index_%' ORDER BY 1,3,4
Cumulative Update: Für den SQL Server stellt Microsoft regelmäßig Updates zur Verfügung. Diese werden nicht automatisch bei Windows Updates bereitgestellt. Meines Wissens werden lediglich Service Packs für den SQL Server in Windows Updates bereitgestellt. Zwischen dem letzten Service Pack und dem letzten Cumulative Update (CU) können in der Realität Jahre liegen. Es ist daher ratsam ab und an den SQL Server zu aktualisieren.
Eine Übersicht der SQL Server Versionen, Service Packs & Cumulative Updates inkl. Downloadlinks findet ihr hier: http://sqlserverbuilds.blogspot.de/
Für die Installation eines Cumulative Update benötigt ihr das davor veröffentliche Service Pack. Bei der Installation wird der SQL Server gestoppt. Es wird also ein Wartungsfenster benötigt. In der Regel dauert die Installation nicht zu lange.
Aufteilung TempDB: Bei der TempDB handelt es sich um eine Systemdatenbank des SQL Servers. Stehen dem SQL Server mehr als ein CPU Kern zur Verfügung, kann die TempDB aufgeteilt werden. Pro lizenzierten Kern sollte es dann eine Tempdb Datei geben. Mit dem folgenden Skript kann die Tempdb aufgeteilt werden:
ALTER DATABASE tempdb ADD FILE (NAME = tempdev2, FILENAME = 'W:\tempdb2.mdf', SIZE = 1024, FILEGROWTH = 1024); ALTER DATABASE tempdb ADD FILE (NAME = tempdev3, FILENAME = 'X:\tempdb3.mdf', SIZE = 1024, FILEGROWTH = 1024); ALTER DATABASE tempdb ADD FILE (NAME = tempdev4, FILENAME = 'Y:\tempdb4.mdf', SIZE = 1024, FILEGROWTH = 1024); GO
Dabei kann die Tempdb auch auf verschiedene Festplatten aufgeteilt werden (siehe oben). Ob das nötig ist, ist im Einzelfall zu betrachten. Die TempDB kann über das SQL Management Studio gefunden werden.
Bei vier Kernen sieht die TempDB nach der Aufteilung so aus.
Alternative Quelle dazu: https://tristanmaritz.wordpress.com/2015/05/15/dynamics-nav-performance-tempdb/
Wartungsjobs: Die Indexierung der Datenbank sollte regelmäßig durchgeführt werden. Dafür ist ein Wartungsjob, der einmal pro Woche durchgeführt werden sollte, empfehlenswert.
Dafür werden für SQL Server 2014 oder ältere SQL Server Versionen ein manuelles Skript empfohlen. Dies ist in drei T-SQL Skripten aufgeteilt.
– T-SQL Skript 1: Umstellung auf Sicherheitsmodell „Simple“
USE [master] GO ALTER DATABASE [Databasename] SET RECOVERY SIMPLE WITH NO_WAIT GO ALTER DATABASE [Databasename] SET RECOVERY SIMPLE GO
– T-SQL Skript 2: Neuindexierung (Rebuild oder Reorganize) der Datenbank
USE [Databasename] GO SET NOCOUNT ON; DECLARE @objectid int; DECLARE @indexid int; DECLARE @partitioncount bigint; DECLARE @schemaname nvarchar(130); DECLARE @objectname nvarchar(130); DECLARE @indexname nvarchar(130); DECLARE @partitionnum bigint; DECLARE @partitions bigint; DECLARE @frag float; DECLARE @command nvarchar(4000); -- Conditionally select tables and indexes from the sys.dm_db_index_physical_stats function -- and convert object and index IDs to names. SELECT object_id AS objectid, index_id AS indexid, partition_number AS partitionnum, avg_fragmentation_in_percent AS frag INTO #work_to_do FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'LIMITED') WHERE avg_fragmentation_in_percent > 10.0 AND index_id > 0;-- Declare the cursor for the list of partitions to be processed. DECLARE partitions CURSOR FOR SELECT * FROM #work_to_do;-- Open the cursor. OPEN partitions;-- Loop through the partitions. WHILE (1=1) BEGIN; FETCH NEXT FROM partitions INTO @objectid, @indexid, @partitionnum, @frag; IF @@FETCH_STATUS < 0 BREAK; SELECT @objectname = QUOTENAME(o.name), @schemaname = QUOTENAME(s.name) FROM sys.objects AS o JOIN sys.schemas as s ON s.schema_id = o.schema_id WHERE o.object_id = @objectid; SELECT @indexname = QUOTENAME(name) FROM sys.indexes WHERE object_id = @objectid AND index_id = @indexid; SELECT @partitioncount = count (*) FROM sys.partitions WHERE object_id = @objectid AND index_id = @indexid;-- 30 is an arbitrary decision point at which to switch between reorganizing and rebuilding. IF @frag < 30.0 SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE'; IF @frag >= 30.0 SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD'; IF @partitioncount > 1 SET @command = @command + N' PARTITION=' + CAST(@partitionnum AS nvarchar(10)); EXEC (@command); PRINT N'Executed: ' + @command; END;-- Close and deallocate the cursor. CLOSE partitions; DEALLOCATE partitions;-- Drop the temporary table. DROP TABLE #work_to_do; GO
– T-SQL Skript 3: Umstellung auf Recovery Mode Full
-- Change security model back USE [master] GO ALTER DATABASE [Databasename] SET RECOVERY FULL WITH NO_WAIT GO ALTER DATABASE [Databasename] SET RECOVERY FULL GO
Ab SQL Server 2016 ist kein spezielles Skript mehr notwendig. Die Wartungspläne “Index neu organisieren” und “Index neu erstellen” können mittlerweile konfiguriert werden. Dabei bleiben die Skripte zur Umstellung des Recovery Modes bestehen. Lediglich das zweite T-SQL Skript (siehe oben) wird durch die Standardwartungspläne ersetzt.
Antivirensoftware: Sollte eine Antivirensoftware auf der Maschine des SQL Servers oder der Dynamics NAV Service Tier installiert sein, dann sollte diese auch richtig konfiguriert werden.Auf dem SQL Server sollte keine Echtzeitüberprüfung auf Systemdatenbankdateien oder normale Datenbankdateien erfolgen. Dazu gibt es auch detaillierte Informationen von Microsoft: https://support.microsoft.com/de-lu/help/309422/how-to-choose-antivirus-software-to-run-on-computers-that-are-running
Auf dem Dynamics NAV Ordner sollten die Programmverzeichnisse von Dynamics NAV ausgeschlossen werden. Bei Performanceproblemen lohnt es sich auch mal, den Vorgang in einer Umgebung ohne oder mit deaktivierten Antivirensoftware nochmal zu testen. Antivirensoftware kann in bestimmten Situationen die Performance massiv verschlechtern.
Hardware: Bei der Hardware gibt es zwei Empfehlungen für Dynamics NAV und den SQL Server:
– Die Hardware (CPU, RAM, Festplatten) sollten exklusiv zur Verfügung gestellt werden.
– Verwendet falls möglich SSD Festplatten für die Datenbankdateien. Das wird die Performance erheblich steigern.Weitere Anforderungen an die Hardware hängen sicherlich von den spezifischen Anforderungen bzw. gleichzeitigen Nutzerzugriffen ab.
Ich hoffe das diese Tipps euch weiterhelfen. Einige Tipps habe ich durch die Zusammenarbeit mit Jörg Stryk und meinen Kollegen in diversen Projekten gelernt. Schaut auf jeden Fall auch bei ihm vorbei: http://blog.stryk.info/.
The post Dynamics NAV / SQL Server Performance Tipps & Tricks first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>heute möchte ich über ein sehr spezielles Thema sprechen. Vor einigen Jahren habe ich das erste Mal davon gehört und als mir ein Kollege dieses spezielle Verhalten von Dynamics NAV erklärte, konnte ich es nicht glauben. Ich erstellte mir direkt eine Testdatenbank und wollte es ausprobieren. Erstaunlicherweise konnte ich das Verhalten sofort reproduzieren und das in verschiedenen Dynamics NAV Versionen. An diesem Punkt realisierte ich, dass dieses Verhalten einen Grund haben muss.
Es handelt sich bei dem Problem um das Rollback Verhalten bei Programmierungen im Validate Trigger eines Tabellenfeldes. Wer das Thema kennt, braucht vermutlich nicht weiterlesen, aber wer jetzt glaubt, das dort gar kein Problem existiert, darf gerne folgendes Experiment mit mir versuchen. Dafür nutze ich eine Dynamics NAV 2017 CU 9 Cronus Demo Datenbank. Das ist zum jetzigen Zeitpunkt das aktuellste Build für Dynamics NAV 2017.
Stellen wir uns folgendes Szenario vor:
Beim Ändern des Feldes “Beschreibung” in der Verkaufszeile soll ein Eintrag in einer neuen Tabelle geschrieben werden. Dieser beinhaltet den Primärschlüssel der Verkaufszeile und eine laufende Nummer, sowie die Beschreibung. Jedes Mal, wenn die Beschreibung geändert wird, soll ein Eintrag geschrieben werden.
Mir ist dabei bewusst, dass es auch die Änderungsprotokollierung in Dynamics NAV gibt, aber die Funktion wollen wir in unseren Beispiel nicht verwenden. Wir öffnen also die Entwicklungsumgebung unserer Dynamics NAV 2017 Demo Datenbank und legen zuerst eine Tabelle an:
Mit der Tabelle haben wir die Grundlage zum Speichern von Daten. Zusätzlich erstellen wir noch eine Page zur Anzeige der Daten. Dafür führe ich folgende Schritte durch:
Nun füge ich die Page in die Actions der Verkaufsauftragssubpage hinzu:
Im Verkaufsauftrag haben wir nun die Action zum Anzeigen des Beschreibungsprotokoll:
Als letztes bauen wir noch eine Anpassung in die Tabelle 37 im Feld “Beschreibung” ein um die Änderungen zu protokollieren.
Diese Anpassung dient nur der Demonstration und ist keine Empfehlung zur Umsetzung. Daran können wir aber feststellen, was das eigentlich Problem ist. Ich ändere nun die Beschreibung von:
Es werden drei Einträge in unserem Beschreibungsprotokoll angezeigt und das ist auch mein gewünschtes Ergebnis. Anscheinend funktioniert alles prima! Nun programmiere ich einen Error in den Modify Trigger der Tabelle 37. Sowas kann man sehr gut vergleichen mit einer Prüfung, ob der Beleg überhaupt “Offen” ist (Beispiel “TestStatusOpen” Funktion). Solche Prüfungen sind in Tabellentriggern nicht unüblich. Zu Demonstrationszwecken nehmen wir hier einen harten Error ohne Bedingung.
Was passiert nun, wenn wir die Beschreibung ein weiteres Mal ändern:
Anschließend verlasse ich die Zeile, indem ich die Pfeiltaste nach unten drücke:
An dieser Stelle bekomme ich nun den Fehler “Stop”, da nun mit dem Verlassen der Zeile der OnModify Trigger der Tabelle aufgerufen wurde. Ich erwarte nun einen kompletten Rollback:
Ich drücke nun F5 und löse den Fehler auf, indem ich meine Änderung verwerfe. Anschließend erhalte ich folgendes Ergebnis:
Wir können klar erkennen, dass der aktuelle Datensatz zurückgesetzt wurde und die Tabelle “Beschreibung Protokollliste” nicht zurückgesetzt wurde.
Was lernen wir daraus? Der Rollback auf der Zeile klappt wunderbar. Der Eintrag in meiner Protokolltabelle ist aber trotzdem vorhanden. Hier fand kein Rollback statt. Warum ist das so? Beim Validieren eines Feldes in einer Page wird der Validate Trigger der Tabelle durchlaufen. Solange der Cursor aber noch in der aktuellen Zeile stehen bleibt, wurde der OnModify Trigger noch nicht durchlaufen. Dynamics NAV speichert aber dennoch alle Änderungen an Fremdtabellen (in unserem Beispiel die Tabelle 50000 “Description Log”) bereits in der Datenbank. Verlasse ich nun die Zeile, wird nur der Rollback auf den aktuellen Record durchgeführt. Alle anderen Transaktionen auf andere Tabellen sind davon nicht betroffen, da diese bereits comitted sind.
Ich vermute, dass dies gewollt ist, damit die Transaktion nicht solange gesperrt wird, wie der Cursor auf dem Datensatz steht. Da zwischen einen Validate in einer Page und dem tatsächlichen Modify auf der Tabelle mehrere Minuten liegen könnten, ist das wohl der Kompromiss. Deswegen spreche ich von der dunklen Seite des Validate Triggers.
Als Faustregel kann folgendes abgeleitet werden:
Ich hoffe das dieser Beitrag einigen Leuten hilft und sensibilisiert.
The post Die dunkle Seite des Validate Triggers first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>im Beitrag Dynamics NAV – Verwendung von Queries habe ich über einen nützlichen Anwendungsfall von Queries (Abfragen) gesprochen. In diesem Beitrag möchte ich über eine weitere Einsatzmöglichkeit von Queries, den generischen Diagrammen, berichten. Wer sich zuerst einlesen möchte, der kann sich den Beitrag im MSDN ansehen (How to: Create Generic Charts). Des Weiteren gib es hier auch eine Einleitung auf deutsch, wie generische Diagramme eingerichtet werden können (Microsoft Dynamics NAV 2013 – neue Auswertungsmöglichkeiten).
In den generischen Diagrammen kann ich grundsätzlich zwischen zwei Datenquellen wählen:
Generische Diagramme bieten sich für eine schnelle Auswertung bestimmter Daten an. In meinem Beispiel befassen wir uns daher mit Kunden- und Lieferanten-Stammdaten. Wir wollen beispielsweise wissen, welche Kunden oder Lieferanten ein Saldo von höher als 1.000 bzw. 10.000 € aufweisen. Wir schauen uns daher die Debitoren mit einem Saldo von mehr als 10.000 € an.
Diese Information steht uns im Microsoft Standard über das Feld Saldo in der Kundentabelle (Tabelle 18 Customer) und in der Lieferantentabelle (Tabelle 23 Vendor) bereits mit dem Flowfield “Balance” (Saldo) zur Verfügung.
Wenn wir nun versuchen unsere Anforderung mit einem generischen Diagramm umzusetzen, dann könnte das so aussehen:
Nun integrieren wir das Diagramm in unserer Rollencenter. Dafür führe ich folgende Schritte durch:
Je nach Anzahl der detaillierten Debitorenposten kann die Performance hier stark schwanken. Ich habe per Stapelverarbeitung 500.000 detaillierte Debitorenposten in einem Cronus Mandanten erzeugt. Trotz meiner sehr leistungsstarken Testumgebung benötigt das generische Diagramm nun ca. 30 Sekunden zum Anzeigen der Daten. Es kann also festhalten werden: Umso mehr detaillierte Debitorenposten vorhanden sind umso länger braucht das Diagramm. Das liegt einfach daran, dass die folgenden Kriterien ungünstig sind:
So sieht das dann für den Anwender aus und das jedes Mal, wenn die Rollencenterseite geöffnet wird. Das ist dann nicht mehr so schön.
Die Alternative dazu stellt eine Query da, die wesentlich schneller ist. Ich orientiere mich am Flowfield, da ich das exakt gleiche Ergebnis haben möchte. So sieht dann meine Query aus:
Die Einrichtung des generischen Diagramms ist fast identisch mit der Einrichtung der Debitorentabelle (siehe oben). Folgende Änderungen sind vorhanden:
Ansonsten sind nur die Feldnamen etwas anders.
Nun können wir das Diagramm im Rollencenter austauschen und müssen dann den Client einmal neustarten:
Nach dem Neustart ist das generische Diagramm sofort geladen. Keine Wartezeit! Hier ist die Query wesentlich schneller als die Flowfield Variante.
Auch wenn ich viel Kritik über Query gelesen habe und es auch berechtigte Kritik gibt, weil es beispielsweise keine Standardintegration von Queries in Seiten oder Berichten als SourceTable gibt, so darf man nicht vergessen, dass Queries in manchen Bereichen sehr große Vorteile aufweisen können.
Das Query Objekt (Debitorensalden) könnt ihr hier herunterladen: 161030_CustomerBalanceQuery.zip
The post Dynamics NAV – Verwendung von Queries #2 – Generische Diagramme first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>mit Dynamics NAV 2013 wurde uns die Objektart Query (MSDN – Queries / MSDN – Working with Queries) vorgestellt und von Microsoft zur Verfügung gestellt. In der letzten Zeit sah ich mehrere alte Umsetzungen oder auch neue Anfragen zu Programmierungen, wo es sich förmlich anbot Queries als Objektart zu verwenden.
Daher möchte ich heute kurz darauf eingehen, wo und wann sich die Verwendung von Queries lohnen kann und habe dafür auch ein Beispiel vorbereitet. Meiner Meinung nach gibt es zwei signifikante Unterschiede zwischen RETEAP UNTIL NEXT Schleifen und Queries:
Wie groß der Unterschied zwischen einer Query und verschachtelten Schleifen oder auch Datasets sein kann, werde ich an folgender Anforderung zeigen:
“Erstelle einen Monatsbericht, der pro Monat und pro Mitarbeiter anzeigt, ob dieser mehr als 8 Stunden gearbeitet hat.”
Das Beispiel ist stark vereinfacht und bezieht sich jetzt auf folgende Tabellen: Ressource, Projektposten, (optional Projektbuchblattzeile). Wir befassen uns nur mit dem Ermitteln der erforderlichen Daten. Die Ausgabe und die Filterung lasse ich komplett außen vor.
Das Dataset könnte beispielsweise so aussehen:
Warum in diesem Fall nicht eine Query nutzen, die mir schon pro Ressource und pro Tag die summierte Menge ausgibt? Die Query muss dann so aussehen:
Wir benötigen die Ressourcennummer, das Buchungsdatum und das summierte Feld Menge. Sobald wir “Method Type” = Totals auf dem Feld Menge einstellen, bekommen alle anderen Felder den Haken “Group by”. Die Ressourcen-Tabelle wird über die Nummer mit der Projektpostentabelle über den DataItemLink verknüpft. Ein Hinweis an dieser Stelle. Wer meint, dass die Ressourcentabelle als DataItem unnötig ist, der hat in diesem Fall Recht. Sobald ich aber beispielsweise den Namen aus der Tabelle nutzen möchte, macht der Aufbau so mehr Sinn und ist daher meiner Meinung nach zielführender. Das war’s, mehr muss man nicht machen. Führen wir die Query einfach mal aus:
Die Abfrage dauert keine 2 Sekunden, obwohl ich am 02.01.2016 1 Millionen Projektposten erstellt habe.
Diese Query integriere ich nun in einen Bericht. Leider weiß ich nicht wie viele Einträge die Query hat, daher habe ich mich entschieden, die Query in eine temporäre Buffer Tabelle zu schreiben und die Buffertabelle dann noch komplett zu durchlaufen (Gibt dazu auch ein HowDoI Video für eine Debitoren TOP 10 Liste: How Do I: Create a Report Using a Query in Microsoft Dynamics NAV 2013 R2)
Die Laufzeit beträgt dann sage und schreibe 2 Sekunden. Für mich ist das ein Unterschied wie Tag und Nacht. Ok, ich habe nun keine Möglichkeit alle Felder der Tabelle für die Filterung des Anwenders zu nutzen, aber das ist meistens auch nicht nötig. Die benötigten Felder kann man entsprechend als Filter in der Query und als Filter in der Requestpage (ausprogrammiert) hinterlegen und das sorgt für einen erheblichen Performanceboost.
Wer es selber ausprobieren möchte, kann die Objekte in einer Cronus DB ausführen. Ihr müsst Euch lediglich vorher genug Projektposten erstellen. FOB und TXT könnt ihr hier herunterladen: 161029_queryimreport.zip
Wer sich damit noch nicht befasst hat, sollte mal einen Blick darauf werfen.
The post Dynamics NAV – Verwendung von Queries first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>kurzes Update zu dem “cumulative Update 9 (CU9)” von Dynamics NAV 2016. Ich hatte gedacht, dass dieses Feature endlich das Problem löst, dass man Rechnungen beispielsweise nicht mehr nach Excel exportieren kann, aber leider bietet das neue Feature nur einen geringen Mehrwert. Warum ich das so sehe, werde ich nun näher erläutern.
Im CU9 wurde ein neues Feature in der Server Konfiguration hinzugefügt:
Damit gibt es drei neue Optionen im Register “Reports”:
Die Option “Report PDF Font Embedding” gab es schon früher und ist damit nichts Neues. Hier kann ich einstellen, ob die Schriftart in der PDF gespeichert werden soll oder ob nur ein Verweis erstellt werden soll. Der Vorteil liegt hier ganz klar in der Größe der PDF. Ich kann die PDF damit etwas verkleinern. Der Nachteil liegt auf der anderen Seite darin, dass bei Barcodes (Lösung per Schriftart) oder individuellen Schriftarten das PDF auf anderen PCs nicht so aussieht, wie es möglicherweise beim Ersteller dargestellt wird, der diese Schriftarten installiert hat. Die gleiche Option gibt es übrigens auch in den Berichtseigenschaften. Als Alternativlösung habe ich bereits über PDF Komprimierung mit Ghostskript berichtet.
Zurück zu den neuen Optionen. Lasse ich alle drei Haken gesetzt, dann verhält sich das System wie gewohnt. Ich sehe die Speichermöglichkeiten in der Requestpage und im Layout:
Setze ich allerdings die Haken “Enable Save as Excel on Request Pages of RDLC-layout Reports” & “Enable Save as Word on Request Pages of RDLC-layout Reports” nicht, so stehen mir die Möglichkeiten auch nicht in der Requestpage zur Verfügung.
Wenn ich den Bericht aber in der Vorschau starte, dann kann ich den Bericht trotzdem als Excel oder Word exportieren. Das bedeutet im Umkehrschluss: Entweder ich setze alle drei Haken oder ich entferne alle drei Haken. Sind alle drei Haken entfernt, dann steht mir das “Speichern” aus der PreView auch nicht mehr zur Verfügung.
Die eigentliche Problematik ist eine Andere. Ich kann nicht entscheiden, ob ich diese Funktionen beispielsweise bei der Rechnung deaktivieren möchte. Vielleicht möchte ich bei einer Kreditor/Artikel Statistik zulassen, dass dieser Bericht einschließlich der Werte nach Excel exportiert werden darf und bei einer Verkaufsrechnung eben nicht. Zusätzlich möchte ich auch pro Bericht entscheiden, ob ein Export per Word, Excel oder PDF in der Vorschau zulässig ist. Bei einer Verkaufsrechnung wird nämlich “Anzahl gedruckt” nur hochgezählt, wenn es nicht in der Vorschau aufgerufen wird. Das ist für mich einer der Hauptgründe, warum Microsoft dort etwas tun sollte.
Ich persönlich kann mit diesem Feature nicht so wirklich etwas anfangen. Klar es gibt vielleicht Einsatzgebiete wo das Sinn machen könnte. Nehmen wir beispielsweise an, dass wir eine eigene Service Tier für das Lager haben und dort möchte ich auch verhindern, dass irgendwer Daten kopierbar aus dem System exportiert. Dann könnte ich diese Optionen bei dieser spezifischen Service Tier deaktivieren. In meinen Projekten bin ich aber nie auf eine solche Anforderung gestoßen. Vielmehr wünscht man sich die berichtsabhängige Einstellung einer solchen Einschränkung.
Wer das genauso sieht und sich eine Option pro Bericht wünscht, der kann hier dafür voten (MS Connect – Enable or Disable Word/Excel/PDF/PreView per Report). Wenn genug Leute voten, dann kann es sein, dass Microsoft sich das für eine der Folgeversionen zu Herzen nimmt. Ich wünsche mir, dass man dafür eine gute Lösung implementiert (ähnlich wie die Funktion PDFFontEmbedding).
Wer das MS Connect nicht kennt, der sollte wissen, dass dies eine Plattform ist, wo “NAV Anwender, Entwickler, Partner, …” Produktwünsche melden können. In regelmäßigen Abständen werden diese Vorschläge dann von Microsoft bewertet und evtl. auch umgesetzt.
Für Fragen oder Feedback schreibt mir doch einfach einen Kommentar!
The post Dynamics NAV 2016 – CU9 – Server Konfiguration RDLC Bericht – Word / Excel Export first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>ich werde immer wieder gefragt, wie man mit Dateien im RTC umgeht! Es haben sich im Gegensatz zum Classic Client einige Dinge geändert. Vieles davon hat sicherlich schon die Runde gemacht und einige Dinge sind evtl. auch noch neu für Euch.
Heute möchte ich kurz erläutern, wie man mit Dateien ab NAV 2009 RTC umgeht und wie man idealerweise eine solide Grundstruktur für automatische Verarbeitungen oder auch für manuelle Verarbeitungen von Dateien schafft. Ich werde das exemplarisch mit NAV 2016 zeigen. Einige Funktionen sind in NAV 2009 evtl. noch nicht vorhanden, aber das Prinzip ist gleich.
Zu aller erst möchte ich daher auf einen wichtigen Punkt hinweisen. Microsoft liefert mit der Codeunit 419 “File Management” schon eine super Auswahl an vielen Funktion aus, die wir ohne Probleme nutzen können.
Auch müssen wir berücksichtigen, dass der C/AL Code in der Regel immer auf der Service Tier ausgeführt wird. Klar kann man beispielsweise .NET Komponenten dazu zwingen auch auf dem Client zu laufen, aber das ist der grundlegende Punkt. Wenn ihr versucht eine Datei mit einer Variable vom Datentyp “File” auf Laufwerk C:\ zu öffnen, dann wird dieser Code auf der Service Tier ausgeführt und dort wird nach der Datei auf Laufwerk C:\ gesucht.
Daher muss man sich hier anders behelfen.
Schritt 1: Dafür erstelle ich nun exemplarisch einen neuen Bericht (R50004) und nenne diesen “Import File”. Des Weiteren stelle ich ProcessingOnly auf Yes, da ich keine Seitenansicht benötige und somit einen “OK” Knopf zum ausführen des Berichtes bekomme.
Schritt 2: Ich lege globale Variablen an, um die Datei clientseitig und serverseitig verarbeiten zu können.
Schritt 3: Erstellung Requestpage, sodass der Anwender eine Client auswählen kann. Dafür erstelle ich eine “ContentArea” eine Gruppe “Optionen” und hinterlege das Feld “FileName”.
Schritt 4: Zusätzlich lege ich nun zwei Textkonstanten an. Diese benötige ich gleich für den Aufruf der Funktion “OpenFileDialog”:
Schritt 5: Im OnAssistEdit rufe ich nun die Funktion “OpenFileDialog” aus dem FileManagement auf. Diese hat drei Parameter:
Vorsicht. Auch im Validatetrigger des Feldes sollte geprüft werden, ob die Datei die richtige Endung hat.
Schritt 6: Testen des Berichts – Führe ich den Bericht aus, öffnet sich die RequestPage, in der ich eine Datei, im Feld “Dateiname” hinterlegen kann. Drücke ich auf die drei Pünktchen, dann öffnet sich der File Dialog. Hier sieht man nun auch, wie mein Filterstring und mein Fenstertitel greifen.
Schritt 7: Nun müssen wir noch ein wenig Programmcode hinzufügen, um die Datei auch hochzuladen (auf die NST) und die Prüfung auf die Dateiendung integrieren:
Ich füge nun also zwei Funktionen “GetFileEnding” und “UploadFile” hinzu:
Schritt 8: Die Funktionen müssen wir nun noch im OnValidate und im OnAssistEdit des Feldes “FileName” integrieren:
Schritt 9: Zu guter letzt können wir nun noch prüfen, ob unser Programmcode funktioniert. Dafür prüfe ich mit “EXISTS”, ob die Datei auf dem Server gefunden werden kann. EXISTS wird nämlich auf dem Server ausgeführt:
Schritt 10: Nun den Bericht zum Test einmal ausführen:
Der Benutzer kann über den AssistEdit Knopf keine falsche Datei auswählen. Versucht der Anwender eine nicht unterstützte Datei direkt in den Dateinamen zu kopieren, wird dies über unsere kleine Prüfung verhindert:
Hier findet ihr den Bericht, wie immer als txt oder als fob:
Für Fragen hinterlasst mir einfach einen Kommentar. In folgenden Beiträgen werde ich mich mit dem Import Excel Dateien, CSV Dateien und anderen Besonderheiten auseinandersetzen.
The post Dynamics NAV – Datei Import/Export #1 first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>heute möchte ich meine Erfahrungen mit der Anbindung von externen Web Services an Dynamics NAV teilen. Dabei muss man grundsätzlich sagen, dass es zwei Formen von Web Services gibt. Zum Einen gibt es SOAP Web Services und zum Anderen gibt es REST Web Services.
Den Unterschied zwischen REST und SOAP kann man hier nachlesen: SOAP vs REST
Wenn Ihr REST Web Services konsumieren möchtet, dann kann ich nur die Serie von “Kauffmann” empfehlen. Hier wird erklärt, wie man das macht:
NAV Techdays 2015 – The Web Service Examples
Web Services Examples Part 1 – The Basic Pattern
Web Services Examples Part 2 – Verify E-mail Address
Web Services Examples Part 3 – Generate Barcode
Web Services Example Part 4 – Send SMS text message
Web Services Example Part 5 – DocuSign Integration
Web Services Example 6 – Office 365 Inbox
Web Services Example 7 – Call NAV OData Web Services (Part 1)
Im Bereich SOAP gibt es auch ein paar Beiträge von Vjeko: Web Services Black Belt: consuming NAV web services using pure C/AL
Hier geht es allerdings um Dynamics NAV Web Services, die durch C/AL konsumiert werden. Wenn man aber nun einen fremden Web Services in Dynamics NAV konsumieren möchte, ist C/AL wohl die falsche Wahl. Zu mindestens trifft das auf mich zu. Ich möchte heute erläutern, wie man einen Web Services mit einer selbst entwickelten Klassenbibliothek konsumieren kann.
Das bedeutet ich werde heute erläutern, wie man eine Klassenbibliothek mit C# in Visual Studio 2015 erstellt, diese dann in Dynamics NAV integriert und den Web Services dann aus NAV heraus konsumiert. Wir lagern also einen kleinen Teil der Business Logik aus und haben dennoch eine Integration in Dynamics NAV. Wer kein Visual Studio besitzt, kann evtl. die Visual Studio Community Edition nutzen Visual Studio CE. Dies ist mit gewissen Auflagen verbunden, bzw. nur bestimmte Personenkreise oder Firmen dürfen das kostenlos nutzen.
Für unser heutiges Beispiel benötigen wir also einen Web Service. Jeder öffentliche Web Services kann dafür verwendet werden. Ich verwende für das Beispiel diesen SOAP Web Service: http://www.webservicex.net/geoipservice.asmx?WSDL
Dieser Web Service gibt uns im Idealfall Informationen zu einer IP zurück. Ich werde exemplarisch zeigen, wie man von einer IP Adresse den Ländercode zurückbekommt. Dafür öffne ich zuerst Visual Studio und erstelle ein neues Projekt und eine neue Klassenbibliothek:
1.) Ist Visual Studio geöffnet über das Menüband ein neues Projekt erstellen: Datei -> Neu -> Projekt…
2.) Im Fenster “Neues Projekt” folgendes tun: Visual C# -> Klassenbibliothek -> Name “SOAPWebServiceConnector”
3.) Ich benenne die Klasse von Class1 in SOAPWebServiveConnector um: Wichtig ist auch, dass die Klasse als “public” gekennzeichnet ist. Ist dies nicht der Fall so haben wir nachher keinen Zugriff auf die Funktionen der Klasse.
Der Vorteil von Visual Studio ist ganz klar, dass ich hier eine Web Referenz hinterlegen kann. Damit ist es mir möglich schnell und effektiv auf alle Funktionen und Klassen eines Web Services zugreifen zu können. Dafür legen wir also eine neue Web Referenz an:
1.) Rechtsklick auf Verweise und die Aktion “Dienstverweis hinzufügen” auswählen:
2.) Im Fenster “Dienstverweis hinzufügen” auf “Erweitert…” klicken:
3.) In den Dienstverweiseinstellungen auf “Webverweis auswählen” klicken:
4.) In der URL die gewünschte SOAP Web Adresse angeben. In meinem Fall ist das: “http://www.webservicex.net/geoipservice.asmx?WSDL” und danach auf den kleinen Pfeil klicken. Nun wird die Web Service Definition importiert und gelesen. Wir hinterlegen zusätzlich einen Webverweisnamen “GeoIPWebService” und klicken dann auch “Verweis hinzufügen”.
In unserer Klasse “SOAPWebServiceConnector” schreiben wir nun ein paar Zeilen Programmcode. Ich lege hier eine öffentliche (von außen erreichbare) Methode GetCountryCodeByIP an.
Innerhalb der Funktion muss ich nun eine Verbindung zu dem Web Service aufbauen und den Ländercode als “return value” zurückgeben.
Nun erstelle und veröffentliche ich meine DLL:
1.) Umstellen des Modus auf “Release”:
2.) Über das Menüband “Erstellen” -> “SOAPWebServiceConnector neu erstellen” auswählen.
3.) Den Projektordner öffnen und die kompilierten Dateien finden:
4.) Je nachdem ob das Addin/Klassenbibliothek Clientseitig (RunOnClient) läuft oder Serverseitig entsprechend im Server/Clientverzeichnis ablegen. In meinem Beispiel muss es im Serververzeichnis abgelegt werden. Benötigt wird lediglich die Datei “SOAPWebServiceConnector.dll”.
Nun ist das Addin / unsere Klassenbibliothek in Dynamics NAV verfügbar. Ich öffne den Entwicklungsclient erstelle eine neue Codeunit und integriere meine Klassenbibliothek.
Ich erstelle hier eine Funktion “GetCountryCodeOfIPAddress und greife damit auf meinen Web Service zu. Ich übergebe eine IP-Adresse und lasse mir am Ende der Funktion per Nachricht den Ländercode zurückgeben. Auch in C/AL muss der Konstruktor einer .NET Klasse aufgerufen werden, damit wir auf die Funktionen der Klasse zugreifen können.
Um die .NET Variable zu definieren, einfach folgende Schritte ausführen:
Zu guter letzt einfach einen Bericht erstellen, der unsere C/AL Funktion aufruft:
Ich bin kein C# oder .NET Programmierer, aber wer hätte gedacht, dass es so einfach ist einen externen SOAP Web Service zu konsumieren. Für mich ist das der leichteste Weg um einen Web Service zu konsumieren. Sicherlich gibt es hier auch noch andere Wege, aber das gibt Euch sicherlich ein paar Ideen, wie man die Anbindung verschiedener Systeme lösen kann.
Hier gibt es wie gewohnt die NAV Objekte und das Visual Studio Projekt:
The post SOAP Web Services konsumieren mit Dynamics NAV first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>nachdem wir uns mit den Einstiegs-Themen zum Thema RDLC beschäftigt haben:
möchte ich nun auf Belegdesign eingehen und wie Belege aus meinen Erfahrungen gestaltet werden müssen. Dabei meine ich mit Belegen, alle Berichte, die eine Kopf-/Zeilenstruktur besitzen. Beispiele sind Einkaufs- und Verkaufsbelege (Verkaufsrechnung, Einkaufsbestellung, …). In meinen heutigen Beispiel gehe ich daher auf ein paar Grundlagen ein und zeige wie man unterschiedliche Kopfgrößen in RDLC realisieren kann!
Ich werde auf verschiedene Dinge eingehen, allerdings keine kompletten Belege zur Verfügung stellen. Wenn ich also nicht auf den CopyLoop eingehe oder andere Dinge (Mehrsprachigkeit, unterschiedliche Währungen, Übertragen von Textkonstanten) eingehe, dann bitte ich dies zu entschuldigen. Falls es dort Bedarf gibt, lasst es mich wissen, dann schauen wir uns das noch in einem späteren Beitrag an.
Zu allererst benötigen wir eine Ausgangsbasis. Dafür habe ich einen neuen Bericht erstellt und ein grundlegendes Dataset erstellt.
Wichtig ist hierbei, dass ich das Bild in einem separaten DataItem übergebe. Das mache ich natürlich aus einem bestimmten Grund:
Darüber hinaus gibt es von Microsoft selbst auch ein paar Tipps zur Performance (blogs.msdn.microsoft.com – RDLC Performance Optimization Tips).
Sollte man beim Erstellen und Testen von Berichten auf leere Seiten stoßen, dann kann ich nur diesen Blog von Natalie empfehlen: nataliesnav.de – Leere Seiten im NAV Report
Des Weiteren habe ich folgenden Code eingefügt:
mit folgenden Variablen:
Mehr brauchen wir im ersten Schritt nicht. Nun werde ich Schritt für Schritt erläutern, wie das Layout aufgebaut werden muss. In Visual Studio stelle ich natürlich wieder das Format auf A4 Hochformat und auf Zentimeter:
Wenn wir uns nun den Kopf eines Berichtes anschauen, dann müssen wir folgendes akzeptieren. Die Kopfgröße ist immer statisch. Es gibt keine Möglichkeit diese Größe flexibel zu gestalten. Habe ich nun einen Kopf, der 5 cm hoch ist, dann kann ich das auf der nächsten Seite nicht verkleinern:
Wir sollten also nicht versuchen, dieses Verhalten zu verändern, da es technisch nicht möglich ist. Aber was ist mit der folgenden Idee?
Warum nicht den Kopf im RDL so gestalten, wie er auf der zweiten, dritten, vierten Seite sein soll. Ich kann doch auch den Textkörper für den großen Kopf der ersten Seite benutzen.
Die große Frage ist nun, wie soll das Ganze funktionieren? Das werde ich nun Schritt für Schritt erläutern.
Einfügen einer Liste in den Textkörper. Eine Liste dient uns als Tabelle und gleichzeitig ein Rechteck. Der große Vorteil? Ich kann einen Seitenumbruch pro Beleg generieren und kann in meinem Rechteck alles platzieren, was auf einem Beleg gedruckt werden soll. Dafür erstellen wir also eine Liste, gruppieren nach der Nummer des Sales Invoice Header und erzeugen einen Seitenumbruch, wenn sich das Gruppenmerkmal “No.” ändert.
Zusätzlich setze ich die Seitennummer in RDL zurück, wenn sich der Beleg ändert und setze einen Filter auf die “No.” damit der erste Datensatz ausgeblendet wird.
Nun erstelle ich einfach einfach einen Kopf für die erste Seite und für die zweite Seite. Alles was auf der ersten Seite nicht in den Kopf passt, packen wir dann in Textkörper. Dafür benutze ich auch SetData / GetData. Diese Funktionen werden benötigt um Daten pro Beleg im Stapeldruck an den Kopf zu übergeben (Im Kopf kann man nämlich nur auf den ersten oder den letzten Eintrag im Dataset zugreifen). Das kann man auch hier nachlesen: SetData / GetData – Why it’s required? und hier: MSDN – SetData / GetData
In dem ersten Beitrag zu SetData/GetData sollte man aber die Geschichte mit der VB Funktion “GetGroupPageNumber” vergessen. Mit Dynamics NAV 2013 R2 ist das Feature “ResetPageNumber” (s.o.) verfügbar und man braucht keinen extra Code dafür. Ich benutze die SetData/GetData-Funktionen aus dem MSDN mit einer kleinen Änderung. Wichtig ist meiner Meinung nach dabei, dass man die Aufrufe in die Hidden-Eigenschaft packt und das SetData immer True als Return Value zurückgibt.
So sieht dann mein Kopfbereich aus:
Ich habe also zwei Rechtecke und habe dort das Bild platziert (erst mal ohne flexible Positionierung) und mit einigen Werten. Ebenfalls blende ich das jeweilige Rechteck je nach der Seitennummer ein. Als Folgeschritt erstelle ich nun den zweiten Teil meines Kopfs auf der ersten Seite im Textkörper.
Ich packe noch ein paar Zeilen dazu und sorge für große Platzhalter um eine zweite Seite zu erzwingen:
Das Ergebnis sieht dann so aus:
Seite 1
Seite 2
So nun hat man zwei verschieden große Köpfe auf Seite 1 und den folgenden Seiten. Hier findet ihr wie immer das Objekt: 160312_RobertsVKRechnung
Falls Fragen bestehen, dann schreibt mir einfach einen Kommentar.
The post RDLC für NAV Entwickler #5 – RDLC Belegdesign – Grundlagen & unterschiedliche Kopfgrößen first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>
in einem meiner letzten Projekte habe ich mit dem NAS automatisiert Lieferscheine gedruckt. Diese werden im Stapel gedruckt und hatten riesige Größen von größer als 30 MB. Diese Lieferscheine sollten dann anschließend per Mail verschickt werden. Wie man sicherlich weiß, gibt es je nach Exchangeserver Limits, die ich mit diesen Dateianhängen überschritten hätte.
Ich möchte daher heute auf eine Möglichkeit eingehen, die wir seit der Einführung von .NET haben (seit NAV 2009). Wir können externe Programme beispielsweise per Eingabeaufforderung aufrufen und das System zwingen zu warten bis auch die externe Verarbeitung, aufgerufen durch die Eingabeaufforderung, fertig ist. Dies möchte ich heute etwas näher am Beispiel von Ghostscript und den dort integrierten Komprimierungsmöglichkeiten erläutern.
Zu aller erst drucke ich dafür in einer NAV 2016 eine Auftragsbestätigung ohne Filtereingaben mit 30 Kopien und speichere diese als PDF. Das soll in diesem Fall mein Kundenbeispiel simulieren und eine große PDF erzeugen. In der “Debitoren & Verkauf Einrichtung” habe ich die Logoposition auf rechts gesetzt, damit die Datei größer wird.
Meine PDF hat jetzt eine Größe von 21,6 MB mit 1302 Seiten und wir sprechen von einem Standardbeleg. Das zeigt schon wohin die Reise gehen kann, wenn die PDF-Logik aus Dynamics NAV verwendet wird.
Ich kann natürlich jetzt versuchen, das Druckbild oder die angedruckten Bilder zu verkleinern. Ich kann auch die Berichtseigenschaft bzw. Servertiereigenschaft: PDFFontEmbedding auf Yes setzen. Aber nehmen wir mal das Worst-Case Szenario an. Ich habe einen integrierten Barcode, belegabhängige Bilder und eine kundenindividuelle Schriftart in meinem Kundenbeleg. In diesem Fall kann ich meine Tricks nicht anwenden und muss also einen anderen Weg finden.
In meinem Fall werde ich nun ein paar kleine Zeilen Code schreiben und das Ganze mit Ghostscript erheblich verkleinern ohne die Qualität groß zu verändern. Zu aller erst installiere ich dafür Ghostscript. Ich verwende die Version gs9.18 – 64 Bit, da ich dies auf der Service Tier (läuft auch mit 64 Bit) ausführen möchte. Nach der Intsallation steht mir hier Ghostscript zur Verfügung:
Ich passe jetzt den Bericht 205 kurz an und setze die Kopienanzahl immer auf 30, damit ich mein Beispiel auch automatisiert nachstellen kann. Dafür füge ich eine neue Funktion ein, damit ich die Anzahl der Kopien von “Außen” setzen kann.
Nun erstelle ich eine neue Codeunit und erzeuge eine PDF über alle Aufträge im System mit jeweils 30 Kopien.
Mit einem RUN auf die Codeunit erzeugt der Server die AB.PDF im Ordner C:\TEMP\ (Bei mir läuft Server und Client auf einer Maschine, daher berücksichtige ich kein 3-Tier File Management).
Nun werde ich die PDF mit Ghostscript und der Eingabeaufforderung extrem verkleinern. Das Ganze berücksichtigt folgende Dinge:
Folgende Variablen benutze ich:
Name DataType Subtype Length
SystemDiagnosticsProcess DotNet System.Diagnostics.Process.’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
SystemDiagnosticsProcessStartInfo DotNet System.Diagnostics.ProcessStartInfo.’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
SystemDiagnosticsProcessWindowsStyle DotNet System.Diagnostics.ProcessWindowStyle.’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
CommandParameter Text
Den Wert “-dNumRenderingThreads=X” sollte man so verändern, wie man es für richtig hält. Dieser gibt an, wie viele CPU-Kerne verwendet werden dürfen.
Die Message zum Schluss gibt das System erst aus, wenn die Komprimierung abgeschlossen ist. Jetzt führen wir das Ganze aus und schauen uns das Ergebnis an:
Wenn das mal nicht ein gutes Ergebnis ist? Dann weiß ich auch nicht mehr weiter! Der Vorgang läuft komplett modal und komprimiert mit einem Faktor von 10 zu 1 bzw. 9 zu 1. Bei Fragen schreibt mir einen Kommentar!
Hier sind die beiden Objekte, falls ihr es selber ausprobieren wollt!
The post .NET – Modale Eingabeaufforderung – Ghostscript Komprimierung von PDF-Dateien first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>Wer den Bericht nutzen möchte, bzw. das Kopieren selbstständig nachvollziehen möchte, der kann sich den Bericht am Ende des Beitrages RDLC für NAV Entwickler #3 – Grundlagen des Layouts – Teil 2 „Der Textkörper“ als FOB oder TXT herunterladen.
Ich möchte nochmal kurz daran erinnern, wie unser Dataset aussieht:
Wir haben das Dataset ohne das Fieldmenü aufgebaut. Wenn bei jedem Feld “Customer.” davorstehen würde, dann wäre der Kopieraufwand viel größer. Man stelle sich das bei einer Verkaufsrechnung vor, die man zu einem Verkaufsgutschrift machen möchte! Des Weiteren habe ich im Namen auch keinen Verweis auf den Debitoren hinterlegt. Das stellt sicher, dass ich auch im Layout nicht verwirrt werde, wenn ich aus der Debitorenstatistik eine Kreditorenstatistik mache und im RDL Layout ständig No_Customer / Name_Customer etc. finde.
Wenn ich nun aus diesem Bericht eine Kreditorenstatistik machen möchte, nehme ich folgende Schritte vor:
Der Bericht sieht dann nicht viel anders aus, als unsere Kundenstatistik und wir haben sieben kleine Schritte gebraucht!
Speichern des Berichts und wir können den Bericht ausführen. So sieht das Ergebnis aus:
Ich habe diese Technik bereits bei Rechnungen, Gutschriften, Angebot, Auftragsbestätigungen und Co angewandt und so erreicht man enorme Einsparungen bei der Entwicklungszeit der Berichte. Bei diesem Bericht habe ich also 5 Minuten gebraucht um eine Kreditorenstatistik aus einer Debitorenstatistik zu erstellen und die Belege sind absolut identisch formatiert. Man kann sich jetzt vorstellen, was das für Belege und Corporate Design bedeutet. Die Belege sind absolut gleich und man muss maximal die Unterscheide im Layout bearbeiten (falls beispielsweise andere Felder im Layout gedruckt werden sollen).
Ich bin gespannt, ob Microsoft auf auch diesen Zug aufspringt, da dies doch echt eine Alternative zu Fremdtools ist, oder?
Für alle die es nicht glauben, können hier die Kreditorenstatistik herunterladen: R50001_Kreditorenstatistik_160228
The post RDLC für NAV Entwickler #4 – Kopieren von Berichten / Layouts first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>Zu aller erst füge ich im Textkörper per Rechtsklick eine neue Tabelle (Tablix) ein.
Dann nehme ich folgende Schritte vor:
Hier die Schritte visuell dargestellt:
Somit ist meine Tabelle vorbereitet. Ich bereite meine Tabelle immer am Anfang vor. Es gibt nämlich Eigenschaften, die später ausgeblendet werden, wenn man alle Zellen markiert und dann noch eine Einstellung verändern möchte. Somit spare ich bereits von Anfang an Zeit und konzentriere mich auch die korrekten Formatierungen. Es ist natürlich jedem selbst überlassen, welche Eigenschaften man verwenden möchte, aber die oben genannten Einstellungen kommen den Classic Einstellungen sehr nahe. Die Schriftart Segoe UI wird ebenfalls vom Standard genutzt.
Ich füge nun 6 weitere Spalten ein. Unsere Tabelle sollte jetzt so aussehen.
Nun können wir uns um die zu druckenden Daten kümmern. Klickt man die Tabelle an, dann hat sieht man eine “Zeilengruppe” Details.
Diese besitzt aktuell die Standardeinstellung. Man kann sich Gruppen im RDL wie OnAfterGetRecords-Trigger vorstellen. Sie laufen nämlich immer über das DataSet. Gruppen sind also die Möglichkeit unsere Datenquelle auch auszugeben. Dabei sind zwei Elemente sind wichtig:
Diese zwei Eigenschaften machen wir uns natürlich zu Nutze. Für unseren Bericht “Debitorenstatistik” benötigen wir zwei Gruppen! Eine Gruppe für die Debitoren und eine ungefilterte Detailgruppe zur Ausgabe der Debitorenposten. Daher füge ich per Rechtsklick auf die Details, eine übergeordnete Gruppe ein.
Die neue Spalte am links, lösche ich gleich wieder, da ich diese Spalte nicht brauche.
Damit haben wir nun die Grundlage für unsere Datentabelle. Ich befülle die Tabelle nun mit Daten. Wie man die Felder anordnet oder wie man diese formiert möchte ich jedem selbst überlassen.
Wichtig ist nun, dass wir die Kopfzeile auf jeder Seite wiederholen und die Kundendaten wiederholen, wenn es einen Seitenumbruch gibt. Dafür muss der “Erweiterter Modus” aktiviert werden.
Anschließend sehen wie auch die “statischen” Zeilen in den Zeilengruppen. Damit der Kopf wiederholt wird und die Kundendaten wiederholt werden, muss die erste statische Zeile und die Kundenzeile mit den folgenden Eigenschaften ausgestattet werden.
Die Eigenschaft “HideIfNoRows” blendet die Überschrift aus, wenn sich keine Datensätze im Filter befinden. Das halte ich in diesem Fall für nicht notwendig, sollte aber berücksichtigt werden, da Tabellen in Berichten nicht gedruckt werden, aber trotzdem Platz reservieren.
Ich speichere das Layout und kann den Bericht nun ausführen. Dabei kann es je nach Datenbasis die unterschiedlichsten kleinen Probleme geben. Beispielsweise, dass Kunden gedruckt werden, die gar keine Posten haben, die Abstände sind zu knapp oder die Formatierungen gefallen einem nicht. Ist man mit allem fertig, dann sollte die Tabelle genau links oben im Textkörper positioniert werden. Ebenfalls sollte es keinen leeren Bereich im Bericht geben. Das würde nämlich nur dazu führen, dass Platz reserviert wird und das kann beispielsweise zu nicht gewollten Seitenumbrüchen führen. Mein Bericht sieht im Ergebnis so aus:
Zum Prüfen oder einfach zum Schauen gibt es hier den Bericht als fob und txt: R50000_Debitorenstatistik_160227
The post RDLC für NAV Entwickler #3 – Grundlagen des Layouts – Teil 2 “Der Textkörper” first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>Zu aller erst sollten wir uns damit beschäftigen, welche Programme wir für die einzelnen Dynamics NAV Versionen benötigen. Dabei gibt es in der Regel zwei Möglichkeiten:
Für eine gute Übersicht und Installationsanleitungen kann man den folgenden Beitrag im MSDynamics.de lesen: Link (Ein Dank an Natalie für die gute Zusammenstellung).
Dazu ergänzend kann man für Dynamics NAV 2016 folgendes sagen: Auch dort funktioniert Visual Studio 2013 Community Edition, Visual Studio 2013 Professional oder der integrierte Report Builder. Ab Dynamics NAV 2016 CU1 (cumulative update 1) wird auch Visual Studio 2015 unterstützt.
Vergleicht man den Report Builder und Visual Studio, dann sollten folgende Fakten berücksichtigt werden:
Es gibt noch mehr Restriktionen, allerdings sind diese drei Restriktionen für mich die Wesentlichsten. Meiner Erfahrung nach eignet sich der Report Builder ausschließlich für Formatierungen oder kleinen Änderungen am Layout. Der Aufbau eines komplexen RDL-Berichtes sollte nicht mit dem Report Builder durchgeführt werden. Die Erstellung einer kleinen Auswertung ist aber dennoch mit dem Report Builder möglich!
Ob sich Visual Studio oder der Report Builder startet, können wir direkt in den Optionen des Dynamics NAV Entwicklungsclient festlegen. Je nach dem, wie wir die Option festlegen steuert NAV, welches Programm beim Aufruf des Layouts geöffnet wird.
Wir setzen uns heute mit Visual Studio 2015 auseinander und wie wir das Layout unserer Debitorenstatistik erstellen können. Nach dem wir das Layout geöffnet haben, sollten wir zu aller erst die Berichtseigenschaften öffnen:
Wie wir sehen, sind die Standardeigenschaften “Seiteneinheiten” auf “Zoll” und Papierformat auf “Letter” eingestellt. Des Weiteren sind in einem neuen Bericht keine Ränder voreingestellt. Es gibt zwar auch die Möglichkeit, eine Vorlage zu laden, wenn ein neuer Bericht erstellt wird, aber darauf gehen wir heute nicht ein. Ich kann nur dazu raten, dass die Einheit auf jeden Fall auf Zentimeter umgestellt wird. Wir können uns alle daran erinnern, dass in Classic Berichten, die Eigenschaften der Größe oder Positionierung einer Textbox in Millimeter angegeben wurden. Würde man nun die Einheit nicht ändern, dann werden alle eingefügten Elemente mit Zoll-Eigenschaften eingefügt. Ändert man die Seiteneinheit, dann werden neu eingefügte Elemente mit Zentimeter eingefügt und es ist wesentlich leichter diese entsprechend eines alten Classic-Berichtes zu übernehmen oder zu vergleichen. Ebenfalls ist die Höhe einer Textbox im Classic Client 423 mm. Im RDL kann ich dann also die Höhe von 4,23 cm hinterlegen. Es ist also sehr hilfreich, wenn diese Eigenschaft von Anfang an geändert wird. Sind bereits Elemente im Bericht enthalten und man ändert die Einstellung, dann gilt diese Änderung nur für neue Elemente.
Des Weiteren können wir hier die Ausrichtung “Hochformat” / “Querformat” angeben! Zusätzlich kann man auch das Papierformat angeben. Hierbei ist folgendes zu berücksichtigen. Im Gegensatz zum Classic Client kann ich hier jede Einstellung vornehmen, die ich benötige. Auch Einstellungen eines Nadeldruckers, trage ich her bei Breite und Höhe manuell ein! Bei der Übertragung des Berichts an den Drucker wird das bestmögliche Seitenlayout gefunden und verwendet. Wichtig ist dabei aber auch, dass die Druckerauswahl bei nicht standardisierten Papierformaten korrekt gepflegt ist. Zu guter letzt können wir unsere Seitenränder einstellen. Im Falle meiner Debitorenstatistik sehen die Eigenschaften dann so aus:
Erst jetzt beginne ich mit dem Hinzufügen von Elementen in meinen Bericht. Standardmäßig starten wir mit einem leeren Textkörper.
Mit einem simplen Rechtsklick in den Textkörper kann ich dann eine Kopf- oder Fußzeile hinzufügen:
In unserem Fall erstelle ich nun eine Kopfzeile. Man beachte dabei, dass der Platz, den die Kopfzeile verbraucht auf jeder Seite reserviert wird, egal welche Sichtbarkeiten ich nachher im Layout hinterlege. Das Gleiche gilt ebenfalls für die Fußzeile. Das ist eine der primären Restriktionen von RDL Berichten. Wie man das zum Teil austricksen kann, darauf werde ich zu einem späteren Zeitpunkt eingehen!
Um nun eine Debitorenstatistik zu erstellen, benötigen wir zwei Elemente:
Kopfzeile:
Hier führe ich folgende Schritte durch:
Mit diesen Schritten habe ich 7 Textboxen mit einem einheitlichen Layout und kann das Design gestalten. Ich achte beim Kopieren auch darauf, dass die Elemente in der gleichen Reihenfolge angeordnet sind, in denen ich die Textboxen eingefügt habe. Danach ordne ich die fünf Textboxen an (vier linkbündig und drei Textboxen rechtsbündig). Dabei verzichte bei der Ausrichtung so weit es geht auf die Maus. Entweder richte ich Elemente mit den Pfeiltasten aus oder ich markiere zwei oder mehrere Elemente und verwende die oben gezeigten Ausrichtungsfunktionen von Visual Studio. Damit sollte man ein wenig herum spielen. Das Layout sieht jetzt so aus:
Folgende Inhalte möchte ich einfügen:
Wer aufgepasst hat, der stellt fest, dass wir den Mandantenname. sowie die Filterungen noch nicht im Dataset aufgenommen werden. Des Weiteren fehlt uns noch das Label PageNoCaption. Die restlichen Informationen haben wir im RDL zur Verfügung. Ich erweitere also das Dataset wie folgt:
Wichtig ist dabei, dass man das Layout neu öffnen müssen, damit die Felder auch in Visual Studio zur Verfügung stehen. Nun hinterlege ich wie oben beschrieben den Ausdruck der Textboxen un benenne die Textboxen gemäß des Ausdrucks um:
Das Ergebnis sieht dann so aus:
Ich kann also sofort über die Dokumentgliederung das entsprechende Element auswählen und bearbeiten. Wenn wir später mit Verschachtelungen arbeiten und wir unterschiedliche Köpfe auf der ersten Seite oder der folgenden Seiten haben, dann werden wir erkennen, warum es Sinn macht, am Anfang etwas mehr Zeit in die Gestaltung unseres Berichtes zu investieren.
Dann speichern wir das RDL Layout und klicken in die DataItem Struktur. Dabei kommt folgende Meldung:
Diese bestätigen wir mit Ja! Dadurch wir das Layout im Entwicklungsclient gespeichert. Des Weiteren stelle ich in den Berichtseigenschaften des Entwicklungsclients den “PreViewMode” auf PrintLayout um. Welche Vor- und Nachteile die beiden Einstellungen (Normal, PrintLayout) haben, werde ich später erläutern. Ich verwende hier das PrintLayout, da ich den Bericht so sehen möchte, wie er später ausgedruckt vorliegt.
Die Vorschau sieht dann so aus:
Damit haben wir den Grundstein unseres Berichtes, den Kopf erstellt und eine Menge über die Grundlagen in Visual Studio gelernt. In folgenden Beitrag werde ich “Best Practice Methoden” für die Datentabelle geben.
Für Fragen oder Anregungen schreibt mir einen Kommentar.
The post RDLC für NAV Entwickler #2 – Grundlagen des Layouts – Teil 1 “Der Kopf” first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>Dieses Feature steht uns in fast allen Pages als Standard zur Verfügung. Es gibt nun aber Pages in denen ich das persönlich doch gerne abschalten möchte. Darunter fallen alle Pages mit aktivierter AutoSplitKey Eigenschaft. Das liegt nämlich daran, dass ich beim Einfügen von neuen Zeilen ein Problem bekomme, wenn die Maske nicht nach dem Feld “Zeilennr.” sortiert ist. Man kennt das Phänomen aus beispielsweise aus dem Verkaufsauftrag. Normalerweise ist die Maske nach “Zeilennr.” sortiert:
Zum besseren Darstellung habe die Zeilennr. als erstes Feld eingeblendet! Sortiere ich nun nach Beschreibung. dann sieht die Maske so aus:
Nun fällt schon auf, dass die erste Zeile die Zeilennr. “40000” und die zweite Zeile die Zeilennr. “20000” aufweist. In diesem Fall wird der AutoSplitKey schief gehen, wenn ich zwischen der ersten und der zweiten Zeile einen neuen Datensatz einfügen möchte.
Das kann nun in jeder Erfassungsmaske passieren. Das Problem ist nun folgendes: Normalerweise wird die Zeilennr. nicht als Feld angezeigt. In der Regel ist das Feld auch vollkommen uninteressant für den Endanwender. Es gibt nun zwei Möglichkeiten:
Ich möchte eigentlich nur den Fehler mit dem AutoSplitKey verhindern. Dafür gibt es einen einfachen Trick. In Masken mit Einrückung steht das Feature nämlich nicht zur Verfügung. Ich muss also nur eine Einrückung simulieren um die Sortierung abzuschalten, aber alle anderen Möglichkeiten zu erhalten. Dafür hinterlege ich in der Subpage auf der RepeaterGroup die Eigenschaft “IndentationColumnName”. Dabei kann ein beliebiger Wert eingetragen werden (beispielsweise “1”):
Nach dem Speichern des Objekts ist die Sortierung nur für diese Page deaktiviert. Alle anderen Features, wie Spalten auswählen, Fixierung oder die Schnelleingabe kann der Anwender selbst steuern. Die Maske hat trotzdem keine Einrückung, verhält sich aber wie die Sachkontenübersicht. Dort kann ich auch nicht umsortieren, da die Maske eine Einrückung berücksichtigt. Es ist sicherlich nicht die schönste Variante, aber es ist eine Möglichkeit, dieses Problem zu lösen.
Probiert es einfach mal aus!
The post Interaktive Sortierung in Pages mit AutoSplitKey Eigenschaft first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>
Aber was ist nun der beste Weg um das Problem zu lösen? Diese Frage muss jeder für sich beantworten. Ich denke alles hat Vor- und Nachteile. Als Vertreter von RDLC Berichten habe ich auch meine Erfahrungen gemacht. Ebenfalls habe ich mich mit den diversen Tools beschäftigt, aber kam nicht um folgende Argumente herum:
Dem Gegenüber steht:
Wie gesagt, das sollte jeder für sich entscheiden, ich halte mich dort an RDLC. Wenn ich einen normalen Arbeitstag betrachte, dann werde ich bestimmt 1x pro Tag zum Thema RDLC befragt. Gefühlt ist hier die Unsicherheit noch groß. Daher möchte ich die Serie RDLC für NAV Entwickler starten, da es gar nicht so schwierig ist, den Einstieg in das Thema zu lernen und ein paar Kernthemen davon zu erläutern.
In meinem ersten Blogeintrag möchte ich mich daher auf das Dataset beziehen und dazu meine Erfahrungen teilen.
Was ist also das Dataset? Im Vergleich zu Classic Berichten, definiere ich die Felder selbst, die ich innerhalb meines Berichtes nutzen möchte. Im Classic standen mir durch die integrierte Lösung, des Section-Designers, auch der Zugriff auf alle meine Felder und Variablen zur Verfügung. Im RDLC müssen wir jedes Feld oder jede Variable explizit angeben, sodass diese im Design an Visual Studio oder an den Report Builder übergeben werden.
Nehmen wir mal an, wir bauen uns eine Debitorenstatistik, die zu jedem Kunden alle Debitorenposten ausgibt. In diesem Fall verbinden wir wie im Classic zwei DataItems (Customer / Customer Ledger Entry) miteinander. Zusätzlich dazu lege ich fest welche Felder ich im Layout nutzen möchte (für Andruck oder für die Sichtbarkeit eines druckbaren Elementes).
Für die Entwicklung nutze ich Dynamics NAV 2016 mit Visual Studio 2015.
Das Ganze könnte dann so aussehen:
Wirft man einen Blick auf den Namen, dann fällt auf, dass ich nicht die eingefügte Beschreibung über das FieldMenü genutzt habe. Das würde nämlich normalerweise so aussehen:
Das mache ich aus den folgenden Gründen. Die Debitorentabelle ist vergleichbar mit der Kreditorentabelle. Wenn ich später aus meiner erstellten Debitorenstatistik eine Kreditorenstatistik machen möchte, dann müsste ich mein Dataset wieder neu aufbauen. Ebenfalls müsste ich Änderungen im Layout vornehmen. Daher halte ich mich an folgende Regeln:
Hält man sich an diese Regel, kann man später aus einem Rechnungslayout ein Gutschriftslayout machen und lediglich die kleinen Änderungen innerhalb der beiden Belege bearbeiten. Das werde ich exemplarisch in einem späteren Beitrag an unserer Debitorenstatistik zeigen.
Zusätzlich zu unseren übergebenen Feldern, müssen wir ebenfalls Captions (Bezeichnungen / Überschriften) übertragen. Dafür gibt es im RDLC drei Möglichkeiten und unterschiedliche Anwendungsfälle:
Klar es gibt auch die Möglichkeit, die Caption hart in die Textboxen im Layout zu hinterlegen. Das ist für mich aber keine Option.
In meinem Fall lege ich nun zwei Labels an und löse den Rest über IncludeCaption. Wir haben in unserem Beispiel keinen Bedarf an Textkonstanten. Des Weiteren gilt: Umso kleiner das Dataset umso schneller läuft der Bericht. Um bei Labels CaptionML zu pflegen muss man dort die Eigenschaften öffnen.
Mein Dataset sieht nun so aus:
Nun kann man sich das Dataset mal anschauen. Dafür führe ich den Bericht einfach aus. Um das Dataset zu betrachten, brauche ich auch kein Layout. Ich möchte lediglich prüfen, ob mein DataSet vollständig ist und das die Informationen so übertragen werden, wie ich es benötige.
Dafür öffnet man den Beleg und wählt über den weißen Pfeil im blauen Feld “Hilfe” aus und “Info zu dieser Seite”. Es öffnet sich ein neues Fenster, welches der Zoom des Berichts darstellt. Dieses Fenster schließen wir wieder. Starte ich nun den Bericht in der Vorschau, dann kann ich mir das Dataset ansehen.
In der geöffneten Seitenansicht, also wieder über den blauen Knopf “Hilfe” und diesmal “Info zu diesem Bericht” auswählen:
Danach öffnet sich das DataSet:
Hier sehen wir nun, das wir die Daten bekommen. Ich möchte kurz auf den Aufbau des Datasets eingehen:
Das Dataset ist also eine große Zusammenführung unserer DataItems und kennt keine Abhängigkeiten mehr. Das bedeutet, wir erstellen in NAV unsere DataItem Struktur um diese beim Designen verwenden zu können. Die Logik, wann wie etwas gruppiert oder gedruckt werden soll, müssen wir im RDLC anhand der Datenbasis des Datasets selbst festlegen.
Wie wir hier sehen können, werden Labels oder die Captions der Felder mit “IncludeCaption” hier nicht angezeigt. Diese werden einmal übertragen und sind nicht Teil unserer Dataset-Struktur. Diese Felder stehen uns in Designer als Parameter zur Verfügung. Darauf gehe ich das nächste Mal ein, wenn ich das Layout und die Beschaffenheit der Elemente etwas näher erläuterte. Ebenfalls sehen wir hier, dass zu jedem Decimalfeld auch eine Formatierung übertragen wird. Diese Formatierung basiert auf der Vorgabe des Tabellenfeldes. In den Einstellungen des Feldes im jeweiligen DataItem kann die Formatierung dann auch nochmal geändert werden.
Bei Fragen schreibt mir einen Kommentar. Ich versuche die Fragen nach meinen zeitlichen Möglichkeiten zu beantworten.
The post RDLC für NAV Entwickler #1 – Allgemeine Informationen & das Dataset first appeared on Robert's Dynamics NAV/BC Entwickler Blog.]]>