Tag Archives: Development

Entity Framework CodeFirst für SQLite

Der ORM Mapper aus dem Hause Microsoft namens “Entity Framework” unterstützt ein sehr nützliches und häufig verwendetes Feature bzw. Ansatz namens “CodeFirst”.
“CodeFirst” ist nichts anderes als die automatische Generierung einer (SQL)-Datenbank, basierend auf dem geschrieben Code (Model). Dabei wird auf convention over configuration gesetzt und so generiert der folgende Code eine Datenbank mit zwei Tabellen inkl. Primary- und Foreign-Keys.

public class Player
{
	public int Id { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public Team Team { get; set; }
}

public class Team
{
	public int Id { get; set; }
	public string Name { get; set; }
	public ICollection<Player> Players { get; set; }
}

In einem laufenden Projekt, an dem ich mitarbeite, soll eine SQLite Datenbank einsetzt werden.

SQLite ist eine Programmbibliothek, die ein relationales Datenbanksystem enthält. SQLite unterstützt einen Grossteil der im SQL-92-Standard festgelegten SQL-Sprachbefehle. Unter anderem implementiert SQLite Transaktionen, Unterabfragen (subselects), Sichten (views), Trigger und benutzerdefinierte Funktionen.

Quelle: Wikipedia

Der Datenbank Zugriff wird mit dem Entity Framework realisiert. Die Entity Framework Unterstützung für SQLite lässt sich über ein NuGet Package hinzufügen: https://www.nuget.org/packages/System.Data.SQLite.

Leider unterstützt SQLite bzw. das SQLite NuGet-Package den CodeFirst-Ansatz nicht. Über diesen Post bin ich auf ein Stück Code gestossen, dass rudimentär den CodeFirst Ansatz implementiert: https://gist.github.com/flaub/1968486e1b3f2b9fddaf

Da ich für das Projekt noch ein paar weitere Funktionalitäten hinzufügen musste und der Code produktiv eingesetzt wird, habe ich beschlossen ein Refactoring durchzuführen und diesen Code wiederum auf GitHub zu veröffentlichen.

Das Resultat findet sich hier:
SQLite CodeFirst
Was alles unterstützt wird und wie die Library verwendet werden kann, ist im GitHub ReadMe beschrieben: Zum ReadMe.

Plug-Ins und Instanzvariablen

Ein Plug-In für CRM implementiert das Interface “IPlugin” welches die Methode “public void Execute(IServiceProvider serviceProvider)” vorschreibt.
Diese Methode wird aufgerufen sobald das Plug-In ausgeführt wird und enthält die Logik. Aber wann wird die Plug-In Klasse instantiiert?

Auf MSDN ist im Artikel Write a plugin-in folgender Text, in einem mit “Important” markierten Abschnitt, zu finden:

For improved performance, Microsoft Dynamics CRM caches plug-in instances. The plug-in’s Execute method should be written to be stateless because the constructor is not called for every invocation of the plug-in. Also, multiple system threads could execute the plug-in at the same time. All per invocation state information is stored in the context, so you should not use global variables or attempt to store any data in member variables for use during the next plug-in invocation unless that data was obtained from the configuration parameter provided to the constructor. Changes to a plug-ins registration will cause the plug-in to be re-initialized.

Zusammengefasst heisst dass ein Plug-In wird einmal instantiiert und danach wird nur noch jeweils die Execut Methode ausgeführt.
Aus diesem Grund sollten keine Instanzvariablen verwendet werden.

Folgendes sollte also nicht gemacht werden.
Das Plug-In wird trotzdem funktionieren, es resultiert aber in einem unerwartetem Verhalten.

public class MyPlugin : IPlugin
{
    private ITracingService tracingService;
    private IPluginExecutionContext context;
    private Entity entity;

    public void Execute(IServiceProvider serviceProvider)
    {
        tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
        context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        entity = (Entity)context.InputParameters["Target"];

        DoThis();
        DoThat();
    }

    private void DoThis()
    {
        // Do something (using tracingService, context and entity)
    }

    private void DoThat()
    {
        // Do something (using tracingService, context and entity)
    }
}

Der Code muss wie folgt geschrieben werden, damit er erwartungsgemäss funktioniert.

public class MyPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        var entity = (Entity)context.InputParameters["Target"];

        DoThis(tracingService, context, entity);
        DoThat(tracingService, context, entity);
    }

    private void DoThis(ITracingService tracingService, IPluginExecutionContext context, Entity currentEntity)
    {
        // Do something
    }

    private void DoThat(ITracingService tracingService, IPluginExecutionContext context, Entity currentEntity)
    {
        // Do something
    }
}

Diese Lösung ist natürlich suboptimal und führt zu unleserlichem Code.
Eine bessere Option stellt die Verwendung einer privaten Klasse dar um die Logik zu kapseln. Somit können Instanzvariablen verwendet werden. Am besten werden gleich alle Abhängigkeiten zum Plug-In abstrahiert. D.h. anstatt den IPluginExecutionContext gebe ich nur die benötigten Eigenschaften über den Konstruktor von “PluginLogic” weiter. Somit lässt sich der Code auch ausserhalb eines Plug-Ins ausführen und/oder mit Unit Tests testen.

public class MyPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        var entity = (Entity)context.InputParameters["Target"];

        var pluginLogic = new PluginLogic(tracingService, context, entity);
        pluginLogic.Run();
    }

    private class PluginLogic
    {
        private readonly ITracingService tracingService;
        private readonly IPluginExecutionContext context;
        private readonly Entity currentEntity;

        public PluginLogic(ITracingService tracingService, IPluginExecutionContext context, Entity currentEntity)
        {
            this.tracingService = tracingService;
            this.context = context;
            this.currentEntity = currentEntity;
        }

        public void Run()
        {
            DoThis();
            DoThat();
        }

        private void DoThis()
        {
            // Do something
        }

        private void DoThat()
        {
            // Do something
        }
    }
}

Build automation und deployment für CRM Solutions mit TFS

In meinem Post “CRM Customizations VS/TFS Integration” habe ich gezeigt wie sich eine CRM Solution in ein Visual Studio Projekt integrieren lässt.
Dafür wird sie entpackt und ihre Komponenten werden auf verschiedene Dateien aufgeteilt. Schlussendlich wird die default Build-Action überschrieben, damit die Solution beim “Build” wieder gepackt wird.

In diesem Post zeige ich, wie die Solution mit einem Buildjob gebuildet und automatisch auf ein CRM System deployt werden kann.
Continue reading

CRM Customizations VS/TFS Integration

Microsoft Dynamics CRM bietet die optimale Grundlage um schnell Projekte abwickeln zu können. Viele Anpassungen an der Applikation erfordern keinen Code. Die Anpassungen werden als „Solutions“, in Form einer ZIP-Datei, exportiert. In Enterprise Szenarien soll jedoch auch für diese Anpassungen die volle ALM Funktionalität von Visual Studio und TFS genutzt werden können.

Continue reading

Shared Projects für Plugins and Workflows

Spätestens wenn man für CRM Online ein Plugin oder eine Custom Workflow Activity entwickelt, kann man keine Assemblies mehr in den GAC oder auf die Disk deployen.
Wie lässt sich nun Code in mehrere Projekte aufteilen oder ein Framework benutzen?

Es gibt drei Optionen.
Option eins und zwei sind aus meiner Sicht nicht empfehlenswert. Der Vollständigkeit halber führe ich sie jedoch auf.

Option 1 Template:
Das CRM Package Project liefert beim erstellen bereits eine Plugin Base Klasse. Das Template kann natürlich angepasst werden.
So hat man mit jedem Projekt sein Framwork mit dabei.
Erstellen von Visual Studio-Vorlagen

Option 2 Linked Items:
Der Code aus einer anderen Library wird als Link in das Projekt eingefügt.
Siehe hier.

Option 3 ILMerge:
Mithilfe von ILMerge lassen sich mehrere Assemblies zu einem mergen.
Davon macht z.B. Log4net gebrauch.

Die Anteilung zeigt wie ILMerge verwendet werden kann.
Dabei habe ich auf die folgenden Kriterien Rücksicht genommen:

  1. Es soll weiterhin das CRM Developer Toolkit verwendet werden könnenn (Rechtsklick –> Deploy).
  2. Debuggen soll weiterhin möglich sein (PDB Merge).
  3. Der Merge muss automatisiert werden.
  4. Die Assemblies müssen mit dem bestehenden SNK signiert werden.
  5. Early-Bound Types müssen weiterhin verwendet werden können.

1. Project-Struktur
Wir haben ein CRM Projekt auf Basis des CRM Developer Toolkits.

ILMerge 1

In diesem Beispiel haben wir vier Projekte. Eines für die Plugins (Plugins), eines für die Workflows (Workflow), eines mit gemeinsam verwendetem Code (Core) und das CrmPackage (CrmPackage). Das CRM Package Project muss das Workflow und das Plugin Project referenzieren. Das Workflow und Plugin Project referenzieren jeweils das Core Project.

2. ILMerge MSBuild Tasks installieren
Das kann ganz einfach über NuGet gemacht werden.

ILMerge 2

3. AfterBuild-Task einfügen
Dem Plugin und dem Workflow Project muss folgender AfterBuild Task hinzugefügt werden.

<UsingTask TaskName="ILMerge.MSBuild.Tasks.ILMerge" AssemblyFile="$(SolutionDir)packagesILMerge.MSBuild.Tasks.1.0.0.3toolsILMerge.MSBuild.Tasks.dll" />
<Target Name="AfterBuild">
<!-- Your configuration goes here -->
<!--- *************************** -->
<!-- Specify the name of the dll files (without file extension) of the referenced assemblies
     which should be merged into the plugin assembly -->
<ItemGroup>
    <ReferencedAssemblyToMerge Include="Core" />
</ItemGroup>
  
<PropertyGroup>
  <SnkFileName>Key.snk</SnkFileName>
</PropertyGroup>

<ItemGroup>
  <!-- The assembly from the current project should also be merged -->
  <MergeAsm Include="$(OutputPath)$(TargetFileName)" />
  <MergeAsm Include="@(ReferencedAssemblyToMerge -> '$(TargetDir)%(identity).dll')" />
</ItemGroup>
  
  <!-- Constants -->
  <!--- *************************** -->
  <PropertyGroup>
    <ILMergeDirectory>ILMerge</ILMergeDirectory>
    <MergedAssembly>$(ProjectDir)$(OutDir)$(ILMergeDirectory)$(TargetFileName)</MergedAssembly>
    <SnkFilePath>..$(SnkFileName)</SnkFilePath>
  </PropertyGroup>
  <ItemGroup>
    <FilesToCopy Include="$(TargetDir)$(ILMergeDirectory)$(TargetName).dll" />
    <FilesToCopy Include="$(TargetDir)$(ILMergeDirectory)$(TargetName).pdb" />
  </ItemGroup>
 
 <!-- Print out informations -->
 <!--- *************************** -->
  <Message Text="ILMerge: Merge '@(MergeAsm)' -&amp;gt; '$(MergedAssembly)'" Importance="high" />
  <Message Text="ILMerge: Sign with '$(SnkFilePath)'" Importance="high" />
  <Message Text="ILMerge: Merge Temp Directory '$(ILMergeDirectory)'" Importance="low" />
  <Message Text="ILMerge: Files to copy from temp '$(FilesToCopy)'" Importance="low" />
  <Message Text="ILMerge: Files to delete after merge '$(FilesToDelete)'" Importance="low" />
  
 <!-- Merge process -->
 <!--- *************************** -->
  <!-- Create temp directory -->
  <MakeDir Directories="$(TargetDir)$(ILMergeDirectory)" />
  <!-- Perform the merge -->
  <ILMerge InputAssemblies="@(MergeAsm)" OutputFile="$(MergedAssembly)" TargetKind="SameAsPrimaryAssembly" SnkFile="$(SnkFilePath)" DebugInfo="True" Closed="True" CopyAttributes="True" />
  <!-- Replace the original file with the merged files -->
  <Copy SourceFiles="@(FilesToCopy)" DestinationFiles="$(TargetDir)%(Filename)%(Extension)" SkipUnchangedFiles="true" />
  <!-- Remove temp directory -->
  <RemoveDir Directories="$(TargetDir)$(ILMergeDirectory)" />
  <!-- Delete the files from the merged projects -->
  <Delete Files="@(ReferencedAssemblyToMerge-> '$(TargetDir)%(identity).dll')" />
  <Delete Files="@(ReferencedAssemblyToMerge-> '$(TargetDir)%(identity).pdb')" />
</Target>

Konfiguration:
MergeAssemblyName: Der Name des Projects welches in das Original-Assembly hinein gemergt werden soll.
SnkFileName: Der Name des Snk-Files, mit dem das gemergte Assembly signiert werden soll.

Erklärung:
Weil weiterhin das CRM Package Project verwendet werden soll, muss das gemergte Assembly, dass Assembly des Projects, (Plugin.dll bzw. Workflow.dll) ersetzen.
Das liesse sich grundsätzlich mit einem einzigen Merge Task erledigen. Will man nun auch das PDB-File mergen (DebugInfo=true) gibt es eine Exception, weil das ursprüngliche PDB-File nicht überschrieben werden kann.
Deshalb wird zuerst ein ein Subdirectory “ILMerge” angelegt und die gemergten Files (DLL und PDB) werden in diesem Ordner abgelegt.
Anschliessend wird das originale DLL und PDB mit den gemergten Files ersetzt und der Ordner gelöscht.
Da nun das Core Assembly zwei Mal im selben Bin Folder liegt, werden z.B. Unit Tests mit der Meldung “ambiguous reference” abbrechen.
Damit das nicht geschieht werden in einem letzten Schritt diese Files gelöscht.

ILMerge Flags:
SnkFile: Damit wird das Assembly signiert. CRM Plugins müssen signiert sein.
CopyAttributes: Damit werden assembly Attribute kopiert. Wenn EarlyBound types verwendet werden, muss das Core Projekt folgende Zeile enthalten: [assembly: Microsoft.Xrm.Sdk.Client.ProxyTypesAssemblyAttribute()]
DebugInfo: Wird hier True gesetzt, wird für das gemergte assembly ein PDB file generiert.