J River Media Center Remote control from Windows Phone 7

14 Sept 2011: MC Remote wins MarketRace competition at Macaw (www.macaw.nl).

A week ago (about the 7th September) MC Remote Pro was published by Macaw on the Marketplace. Since then more the 700 people downloaded the App. I was joining in a competition at Macaw’s called the MarketRace. MC Remote won the competition. I would like to thank all that have contributed to the success I had with MC Remote. Especially the people at J River (Jim Hillegas, Matt Ashland and others) for their support. Thanks all.

I’ve retreated MC Remote from the Marketplace. The PRO Version will be published soon for free by Macaw (www.macaw.nl).

SNAGHTMLfa1105

Although I don’t even have a Windows Phone (yet) I’ve started developing a Remote Control for Media Center Versions 14 and up based on the Web Service in Media Center. Above and below you can see some Images caught from the Emulator.

The project is hosted on SourceForge and can be found here.

The App has reached maturity. This is the functionality until now:

  • Settings for connecting to the server are stored in IsolatedStorage (meaning that with each start the settings can be retrieved from the memory of the phone)
  • Routines for reaching the server are working as expected
  • The App retrieves current playing info from the server and displays it. It also retrieves the image from the server (with some nice mirror reflection)
  • Buttons for Previous, Play/Pause, Stop and Next are working. Took the Layout from the Thunderstorm skin from MC)
  • Zones can be toggled and show information / search etc. accordingly
  • Added Page transition animations (see video)
  • Added Application bar (at the bottom) with menus
  • Restyled the App. Panorama view interfered with the sliders
  • Playlists are shown and can be played (as new or as next to play)
  • The Search panel is working. Play (as next) too.
  • In the Search panel multiple items can be selected and played (as new or as next).
  • Implemented the View page. From here MC can be controlled. The view can be set to Normal, Mini, Display, Theater or Cover.
  • Styled the navigation buttons using the MC Logo (cool). The images are a bit edgy.
  • Click and hold on a button now automatically repeats the action until released.
  • Planning for 2 versions.
    • Lite Free version with play control, Playlists and search (with only 1 item selectable).
    • Pro Paid version with all above and search with multiple selection and the new View Page.

Wishlist

Wish

Status

Support Zones Implemented
Support Search for Artist/Album/Song Implemented
Support for Access Keys Missing info
Support for LAN, WAN access and WOL Missing info

SNAGHTMLfc2ffaSNAGHTML2fad9abSNAGHTML1b64b5aSNAGHTMLff2d5bSNAGHTML1083853SNAGHTML1076720SNAGHTML1b6cc6bSNAGHTML1b774abSNAGHTMLfe786e

AutoEQ and AutoSkypeMute

Some years ago I developed 2 plugins for JRivers Media Center called AutoEQ (a plugin that automatically adjusts the Equalizer of MC using a preset value in the songs) and AutoSkypeMute (a plugin that reduces volume to a preset on receiving Skype calls). A problem with these plugins was that for each new version of MC I had to deliver a new plugin too which over time proved to be too much to handle for me. I’ve now changed the plugins in such a way that when JRiver does not change anything to the interfaces to methods and leaves the COM GUID’s unchanged the plugins will function with all future version too. Below you can find the downloads for these plugins. Have fun!

AutoEQ download
AutoSkypeMute download

These plugins are now also available on SourceForge.

AutoEQ on SourceForge
AutoSkypeMute on SourceForge

Remigreren

Het is dan dus zover, we verhuizen terug naar Nederland. Na 7 1/2 jaar Zwitserland komt er een einde aan ons buitenlands avontuur. Het ontslag hier in Zwitserland heeft alles op zijn kop gezet en in een stroomversnelling gebracht. Ik ben blij dat ik bij Macaw(.nl) een fantastiche job heb gevonden in het nieuw te vestigen filiaal in Apeldoorn.

Onze verhuizing zal vermoedelijk half juni plaats gaan vinden. Het grootste deel van onze spullen zal worden opgeslagen want we nemen eerst intrek in een vakantiehuisje in Beekbergen. Vanuit daar gaan we op zoek naar onze definitieve woonplek in of in de buurt van Apeldoorn op de prachtige Veluwe.

Wij verheugen ons op de terugkeer naar ons geboorteland. En ik persoonlijk heb erg veel zin in mijn nieuwe job bij Macaw. Ik wordt daar Project Manager bij MAS (Application Services).

De gevoelens die ik heb zijn wisselend. Aan de ene kant vind ik het jammer dat we Zwitserland verlaten. Het land is prachtig en de mensen zijn ontzettend vriendelijk. Als ik door Zürich loop krijg ik weemoed wanneer ik denk dat ik dit ga verlaten. Ook ons dorpje Dottikon waar we nu nog wonen heeft zijn charme. Voor ons is het nu een uurtje rijden naar de bergen en daar is het geweldig. Als je op 3000 meter hoogte op zo’n berg staat en de wereld van boven kunt bekijken geeft dat een gevoel van vrijheid.

Aan de andere kant is het idee weer in de buurt van familie en vrienden te zijn heerlijk. Het onthaal dat we tot nu toe hebben gehad is zeer warm geweest. Het lijkt wel of iedereen blij is dat we weer terugkomen.

Het idee dat we straks weer gewoon de hele dag Nederlands kunnen praten is ook iets waar ik wel weer naar verlang. Hier heb ik Zwitserduits leren verstaan en spreken en dat was erg leuk. Ik kan nu erg goed met mijn collega’s discussieren. Mijn Duitse kennis en ervaring zijn hier duidelijk verbeterd. En ik verheug me natuurlijk op de simpele dingen van het leven zoals hagelslag, pindakaas en drop. Dat soort dingen vind je hier niet en hebben we af en toe geimporteerd vanuit Nederland.

The Entity Framework does not work well with inheritance

This post is to warn you.

We’ve been building a .NET Application with WPF, C#, T4, MSSQL, Entity Framework and a product called DevForce from IdeaBlade.

The Project is set up to be completely object oriented and also uses a lot of inheritance. The OO design aims at code-reuse, flexibility and high quality. Minimizing code redundancy is of course one of the main pillars of OO and I think we can say that so far we’ve succeeded at that.

Last year after carefully studying and testing the Entity Framework we came to the conclusion we could use it in our project. We had however tested without inheritance in the conceptual model in the EF (v3.5). For our C/S needs we decided upon DevForce of IdeaBlade. What DevForce, out of the box, delivers is a solid environment for development, with a better object mapper than .NET 3.5 was delivering, a flexible cache, a large library with lots of functionality (List Managers, Validation, Cache management, etc). What is realy sweet is the fact that you develop a fat client and than later decide you would like a thin client with a server. A tool delivered with DevForce simply splits the assemblies into a Client and Server deployment and your done (well almost).

Our first experiences with DevForce and EF were quite positive and we went along to develop a, if I may say so, nice client application. Along the way the application became more complex and more and more navigation properties were added to the Conceptual and Datamodel. Performance became an issue. We discovered that the Entity Framework does not work well with entities that use Inheritance. We asked Microsoft for guidance and the simple but disturbing answer was: “Just because you can does not mean that you should” and “Don’t do that!”.

In the mean time performance was down to a crawl. We’ve tried everything. Used ‘Includes’ in the queries. That made performance even worse. Some queries went from seconds to minutes. The SQL produced by the EF was unreadable and huge (some queries mounted up to 6 Mb!!). We tried pre-loading reference tables. That gave some relieve but still loading 1 Dossier in our application took over 40 seconds. We’ve implemented TPH and TPC but only little relieve. The cache of DevForce was helpfull but could of course not solve the problem on it’s own.

Then we went to .NET 4.0. We had read that performance was up 25% which was nice but not nearly enough to get our application fast enough.

So here we are. A lot of investment into our application and we’ve come to a complete standstill. After consulting IdeaBlade (these people really are constructive thinkers) we all came to the conclusion that the combination of DevForce and Entity Framework was not the right solution for our project.

Today we decided to go with NHibernate. Luckily our code-base is split up in several tiers so we only need to exchange the Data Layer (being DevForce and the Entity Framework). Our ViewModel talks to the Data Layer and does not change at all. We only need to change the way our Data Layer talks to NHibernate. Our preliminary tests show that NHibernate is about 250 times faster than the EF with Inheritance. For example: Loading all data needed for 1 Dossier took over 6 seconds with EF and 286 ms with NHibernate on the first load. The second load of a dossier still takes 280 ms in EF and under 1 ms with NHibernate.

Of course the story doesn’t end here. We are going to miss the functionality of DevForce painfully but there are tools around that can solve that too (NCache for instance).

The clear warning of this post is: If you need inheritance in your conceptual model don’t use the Entity Framework or you’ll be sorry.

28.05.2010 Correction

We’ve still not decided to go with NHibernate at the moment. We’re looking at several candidates for our project. Here’s a list of the Candidates that remain from the 23 listed on WikiPedia:

03.06.2010 Correction

We decided to go with DataObjects.Net.

06.06.2010 Enhancement

Pursuing the matter a bit further, I wrote 2 tests. 1 For EF and 1 for DO4. Both write and read 150.000 rows from a model with deep inheritance. We use ‘Table per Type’. Here’s the Model.

image

There are 3 AbstractEntities. 02 inherits 01, 03 inherits 02, Sample inherits 03. This is the deepest inheritance level.

Each of the NavigationEntities01 thru 15 inherits AbstractEntity01.

Both tests write/read 100 rows to/from Sample (meaning to/from AbstractEntity01 thru 03 as well). Each Sample row has 15 Navigation properties (Navigation01 thru 15). Each test writes/reads 100 rows to/from each Navigation property.

The DO4 test uses the Prefetch() extension method. This causes the fetch of all navigation properties during the initial fetch. I’ve tried implementing the Include() extension method of the EF but that resulted in a timeout from the DB and a very, very long execution time.

On the second load the DO4 2-level cache kicks in.

Both test were written using VS2010 Professional and .NET 4.0.(DO4 is still at .NET 3.5 at the moment of this writing).

Here are the results:

image

EF vs DO4

EF

DO4

Insert 150000 rows

109994

49533

Reading 150000 rows first load

24746

9830

Reading 150000 rows second load

14818

530

Times are in milliseconds. You don’t need much fantasy to see that DO4 is the clear winner here!

DataObjects Inheritance Test (zip)

EF Inheritance Test (zip)

WPF browser Application with DataObjects.Net 4.3RC

Today started a new challenge. I’m going to write an WPF Browser Application called the ‘AnyMatrix’ (small hint to what my movie taste is). The first results are encouraging. (http://ormmatrix.sinnema.ch). A TabControl and a button (that does nothing yet) is visible (will change in the near future). I’m going to use DataObjects.Net 4.3RC as my Data Access Layer. So it’s going to be a test for that too.

What I aim at is a website on which I can host any comparison matrix (like the cmsmatrix). The first Matrix is going to be the ORM Matrix.

Here I’ll describe the struggles I’ve encountered so far. Here’s the first one:

Created a WPF Browser Project and simply started it. Wow, that is nice. Out of the box it just works locally. WPF is a huge step forward. Wonder if the Application I build will work out-of-the-browser too. Guess that should be possible.

Added a TabControl a few TabItems and a button. Works fine too.

Now for the Deployment. Would like to see if my provider (http://www.simplehosting.ch) supports this kind of applications.

MIME Types

Had to add some MIME Types to the configuration. Plesk does a good job at that. Here’s a link on what to configure: http://msdn.microsoft.com/en-us/library/ms752346.aspx.

Added a HTML main page like so:

<html> 
  <head></head> 
    <frameset cols="100%,100%"> 
      <frame src="AnyMatrix.xbap" > 
  </frameset> 
</html>

Copied the needed files to the Web Server. Here’s a link on what to copy: http://msdn.microsoft.com/en-us/library/aa970060.aspx

Called my link: http://ormmatrix.sinnema.ch and voila, the page is there, impressive.

Just discovered it does not work in Chromium (not Chrome).

Dataobjects.Net 4

Now I’ll try and get DO4 to work. Added a DO4 Project to my solution. After writing some code to initialize the Domain got an exception I don’t fully can grasp. It is thrown in the Getter of the Domain where the .Build() is called.

Inheritance security rules violated while overriding member:

‘Xtensive.Storage.Entity.System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)’. Security accessibility of the overriding method must match the security accessibility of the method being overriden.

Here^s my Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xtensive.Storage.Configuration;
using Xtensive.Storage;

namespace AnyMatrixModel
{
    public class DomainManager
    {
        private static DomainConfiguration m_DomainConfiguration;
        private static Domain m_Domain;
        private static Session m_Session;

        public static DomainConfiguration DomainConfiguration
        {
            get
            {
                if (m_DomainConfiguration == null)
                {
                    m_DomainConfiguration = new DomainConfiguration("sqlserver", "Data Source=DIARTIS-SQL1\\SQL2008;Initial Catalog=ORMMatrix;Persist Security Info=True;User ID=xxxxxxxxx;Password=xxxxxxxxxx;MultipleActiveResultSets=True");

                    m_DomainConfiguration.Types.Register(System.Reflection.Assembly.GetExecutingAssembly(), typeof(Person).Namespace);
                }

                return m_DomainConfiguration;
            }
        }

        public static Domain Domain
        {
            get
            {
                if(m_Domain == null)
                {
                    try
                    {
                        m_Domain = Domain.Build(DomainManager.DomainConfiguration);
                    }
                    catch (Exception x)
                    {

                        throw;
                    }
                }

                return m_Domain;
            }
        }

        public static Session Session
        {
            get
            {
                if(m_Session == null)
                {
                    m_Session = Session.Open(DomainManager.Domain);

                    DisconnectedState disconnectedState = new DisconnectedState();

                    disconnectedState.Attach(m_Session);
                }

                return m_Session;
            }
        }
    }
}

Running the application in ‘Full Trust’, as suggested by X-Tensive (see here) solved the exception, but then I got the next one.

No upgrade handler is found for assembly ‘OrmMatrix’, version ‘1.0.0.0’.

After changing the above getter for the DomainConfiguration and the App.config like so:

        public static DomainConfiguration DomainConfiguration
        {
            get
            {
                if (m_DomainConfiguration == null)
                {
                    m_DomainConfiguration = DomainConfiguration.Load("Default");
                }

                return m_DomainConfiguration;
            }
        }
<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="Xtensive.Storage" type="Xtensive.Storage.Configuration.Elements.ConfigurationSection, Xtensive.Storage" />
  </configSections>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>

  <Xtensive.Storage>
    <domains>
      <domain name="Default"
              provider="sqlserver"
              upgradeMode="Recreate"
              connectionString="Data Source=DIARTIS-SQL1\SQL2008;Initial Catalog=ORMMatrix;Persist Security Info=True;User ID=xxxxxxxxxx;Password=xxxxxxxxxx;MultipleActiveResultSets=True" >
        <types>
          <add assembly="AnyMatrixModel" />
        </types>
      </domain>
    </domains>
  </Xtensive.Storage>
</configuration>

The Application started running as expected (locally).

Setting up a project with centralized model info using DataObjects.Net.

In our project KLIB we use centralized model information. Our 5-tier application (View, ViewModel, Model, DataLayer, DB) uses T4 to generate the partial classes for the Model, ViewModel. The management of the DB Tables is done for us by DataObjects.Net. DataObjects.Net is capable of detecting model changes from the meta-data from the DB and the model in code.

We structured all our generation around the model-info which is based in the hand coded model classes.

Here’s a small part of our model for Contact. You can see the Person as part of it. The complexity of the Contact model lies in the fact that we have to be able to create a history of contacts (on which I will not elaborate here).

Contact

What you see below is our definition of the model in our own attributes at the start of the class (in this case the Person class called ‘Person.c’, note this definition is not complete because the Person class inherits the Contact class which is not described here, therefore there is no key in the description present.).

    #region EntityMembers
    [EntityClassAttribute]
    [EntityMemberAttribute(MemberName = "PersonnelNumber", Type = typeof(string))]
    [EntityMemberAttribute(MemberName = "Nickname", Type = typeof(string))]
    [EntityMemberAttribute(MemberName = "FirstNames", Type = typeof(string))]
    [EntityMemberAttribute(MemberName = "BirthDate", Type = typeof(Date))]
    [EntityMemberAttribute(MemberName = "DeathDate", Type = typeof(Date))]
    [EntityMemberAttribute(MemberName = "AHVNumberOld", Type = typeof(string))]
    [EntityMemberAttribute(MemberName = "AHVNumberNew", Type = typeof(string))]
    [EntityMemberAttribute(MemberName = "Title", Type = typeof(string))]
    [EntityMemberAttribute(MemberName = "IsUnknown", Type = typeof(bool))]
    [EntityMemberAttribute(MemberName = "WantsToStayAnonymous", Type = typeof(bool))]
    [EntityMemberAttribute(MemberName = "IsUser", Type = typeof(bool))]
    [EntityMemberAttribute(MemberName = "WindowsLogonName", Type = typeof(string))]
    [EntityMemberAttribute(MemberName = "UILanguage", Type = typeof(string))]
    [EntityMemberAttribute(MemberName = "CivilStatusList", Type = typeof(EntitySet<CivilStatus>), BackTo = "Person", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "ToDocumentPersonAuthorList", Type = typeof(EntitySet<DossierDocument>), BackTo = "Author", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "ToDocumentPersonSenderList", Type = typeof(EntitySet<DossierDocument>), BackTo = "Sender", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "ToAuthorPersonList", Type = typeof(EntitySet<DossierMeeting>), BackTo = "Person", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "DossierMeetingPersonList", Type = typeof(EntitySet<DossierMeetingPerson>), BackTo = "Person", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "DossierTaskList", Type = typeof(EntitySet<DossierTask>), BackTo = "Originator", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "Gender", Type = typeof(Gender), IsNullable = true)]
    [EntityMemberAttribute(MemberName = "LastNameList", Type = typeof(EntitySet<LastName>), BackTo = "Person", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "NationalityList", Type = typeof(EntitySet<Nationality>), BackTo = "Person", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "PersonDossierAppointmentList", Type = typeof(EntitySet<PersonDossierAppointment>), BackTo = "Person", IsOwned = true)]
    [EntityMemberAttribute(MemberName = "PersonDossierTaskList", Type = typeof(EntitySet<PersonDossierTask>), BackTo = "Person")]
    [EntityMemberAttribute(MemberName = "PersonEmploymentSituationList", Type = typeof(EntitySet<PersonEmploymentSituation>), BackTo = "Person", IsOwned = true)]
    #endregion
    [Serializable]
    public partial class Person : Contact, IDisposable, IComparable
    {

    }

Below is the definition of our so called AbstractEntity class from which all our model classes inherit. Here you see the key definition too (‘Id’) which results in a KeyGenerator being generated in code (see below).

    #region EntityMembers
    [EntityRootClassAttribute]
    [EntityMemberAttribute(MemberName = "Id", Type = typeof(int), IsKey = true, IsNullable = false)]
    [EntityMemberAttribute(MemberName = "CreatedOn", Type = typeof(DateTime), IsStamp = true, IsNullable = false)]

    // The CreatedBy and ChangedBy are added as ints. If we would add Person here, any Person ever having made a change
    // to the DB can not be deleted anymore (referential integrity).
    // Further would the construction be very costly from a performance point of view.
    [EntityMemberAttribute(MemberName = "CreatedBy", Type = typeof(int), IsStamp = true, IsNullable = false)]
    [EntityMemberAttribute(MemberName = "ChangedOn", Type = typeof(DateTime), IsStamp = true, IsNullable = false)]
    [EntityMemberAttribute(MemberName = "ChangedBy", Type = typeof(int), IsStamp = true, IsNullable = false)]
    [EntityMemberAttribute(MemberName = "IsDefault", Type = typeof(bool))]
    #endregion
    [Serializable]
    public abstract partial class AbstractEntity : Entity, INumberedObject, IValidatable, IDeletable
    {
    
    }

 

We wrote a library that uses the EnvDTE of Visual Studio to find and interpret the attributes in all the hand coded model classes. The library is used as an assembly in the T4 scripts. Here’s the start of our T4 script for our code model (our CodeModel assembly is call Diartis.KLIB.CodeModel).

<#@ template language="C#" hostSpecific="true" debug="true" #>
<#@ output extension="txt" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ assembly name="System.Windows.Forms" #>
<#@ assembly name="System.Core" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="%ProgramFiles(x86)%\DataObjects.Net\Bin\Latest\Xtensive.Storage.Rse.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="%ProgramFiles(x86)%\DataObjects.Net\Bin\Latest\Xtensive.Integrity.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="%ProgramFiles(x86)%\DataObjects.Net\Bin\Latest\Xtensive.Core.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="%ProgramFiles(x86)%\DataObjects.Net\Bin\Latest\Xtensive.Storage.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="$(SolutionDir)..\KLIBLibrary\bin\Debug\KLIBLibrary.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="$(SolutionDir)KLIBCodeModel\bin\Debug\KLIBCodeModel.dll" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Windows.Forms" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Diagnostics" #>
<#@ Import Namespace="Diartis.KLIB.CodeModel" #>
<#@ Import Namespace="Diartis.KLIB.CodeModel.CodeModelAttributes" #>
<#@ Import Namespace="Diartis.KLIB.KLIBLibrary" #>
<#@ Include file="FileOutputManager.ttinclude" #>
<#@ Include file="ModelHelper.tt" #>
<#
    try
    {
#>    #region Prepare code model
<#
        // START main try
        // Initialization Output Manager
        Manager outputManager = Manager.Create(Host, GenerationEnvironment);
#><#
        // Start Costruction CodeModelThree
        IServiceProvider hostServiceProvider = (IServiceProvider)Host;
        DTE2 dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.10.0");

        string templateFileName = Path.ChangeExtension(Path.GetFileName(Host.TemplateFile), ".cs"); //this is the fileName of the generated cs

        string directoryPath = Host.ResolvePath("..\\..\\KLIBDatabase");
        DirectoryInfo dir = new DirectoryInfo(directoryPath); // in this path are all classes for that the DO/KLIB classes should be generated
        FileInfo[] files = dir.GetFiles("*.cs");
        CodeModelManager codeModelManager = new CodeModelManager();

        foreach (FileInfo file in files)
        {
            if(!file.Name.Equals(templateFileName)) //don't try to parse the output of this transformation
            {
                //dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.10.0");
                ProjectItem inputProjectItem = dte.Solution.FindProjectItem(file.Name);  //try catch besser
                if (inputProjectItem != null)
                { // START inputProjectItem not null
                    FileCodeModel2 codeModel = (FileCodeModel2)inputProjectItem.FileCodeModel;
                    codeModelManager.AddClass(codeModel);
                } // END inputProjectItem not null

            }
        } // fileInfo foreach end

        List<CodeModelClass> classList = codeModelManager.Classes;
        // End Costruction CodeModelThree

        bool PreloadReferenceEntities = false;

#>        #endregion

As you can see we use the ‘VolatileAssemblyProcessor’ which comes from the T4ToolBox from Codeplex (http://t4toolbox.codeplex.com/). You don’t really need the T4ToolBox but it make life easier. After the generation process finishes, all assemblies that are in the script remain in memory and are locked on disk. This causes the build of the CodeModel classes to fail because the compiler can not replace them. You will have to restart Visual Studio each time you would like to generate the CodeModel library which is annoying to say the least.

The next step in T4 is to start generating the code using the input of the CodeModel library (meta data from our attributes).

<#        if (classList.Count > 0)
        { // START classList > 0
            foreach(CodeModelClass modelClass in classList.Where(c => c.IsOfTypeDBClass))
            { // START foreach CodeModelClass
                // START writing partial class file
                outputManager.StartNewFile(modelClass.Name + ".Generated.cs");
#>

// Generation of your code goes here...

<#             outputManager.EndBlock();
            outputManager.Process(true); // Write files to disk
        } // END classList > 0
    } // END main try

This is what, part of, the generated Person class looks like (called Person.generated.cs). The generated attributes are for DataObjects.Net.

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Company: Diartis AG
//     Website: www.diartis.ch
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Diartis.KLIB.KLIBDatabase;
using Diartis.KLIB.KLIBLibrary;
using Diartis.KLIB.KLIBLibrary.Debugging;
using Diartis.KLIB.KLIBLibrary.Exceptions;
using Diartis.KLIB.KLIBLibrary.Interfaces;
using Diartis.KLIB.CodeModel.EntityAttributes;
using Xtensive.Storage;
using Xtensive.Storage.Configuration;
using Xtensive.Core.IoC;

namespace Diartis.KLIB.Model
{ // START class namespace
    public  partial class Person : Contact , IComparable
    { // START partial class
        #region DataObjectsFields

        [Field(Nullable = true), FieldMapping("PersonnelNumber")]
        public string Z_PersonnelNumber { get;  set; }

        [Field(Nullable = true), FieldMapping("Nickname")]
        public string Z_Nickname { get;  set; }

        [Field(Nullable = true), FieldMapping("FirstNames")]
        public string Z_FirstNames { get;  set; }

        [Field(Nullable = true), FieldMapping("BirthDate")]
        public DateTime? Z_BirthDate { get;  set; }

        [Field(Nullable = true), FieldMapping("DeathDate")]
        public DateTime? Z_DeathDate { get;  set; }

        [Field(Nullable = true), FieldMapping("AHVNumberOld")]
        public string Z_AHVNumberOld { get;  set; }

        [Field(Nullable = true), FieldMapping("AHVNumberNew")]
        public string Z_AHVNumberNew { get;  set; }

        [Field(Nullable = true), FieldMapping("Title")]
        public string Z_Title { get;  set; }

        [Field(Nullable = false), FieldMapping("IsUnknown")]
        public bool Z_IsUnknown { get;  set; }

        [Field(Nullable = false), FieldMapping("WantsToStayAnonymous")]
        public bool Z_WantsToStayAnonymous { get;  set; }

        [Field(Nullable = false), FieldMapping("IsUser")]
        public bool Z_IsUser { get;  set; }

        [Field(Nullable = true), FieldMapping("WindowsLogonName")]
        public string Z_WindowsLogonName { get;  set; }

        [Field(Nullable = true), FieldMapping("UILanguage")]
        public string Z_UILanguage { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Person")]
        public EntitySet<CivilStatus> Z_CivilStatusList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Author")]
        public EntitySet<DossierDocument> Z_ToDocumentPersonAuthorList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Sender")]
        public EntitySet<DossierDocument> Z_ToDocumentPersonSenderList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Person")]
        public EntitySet<DossierMeeting> Z_ToAuthorPersonList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Person")]
        public EntitySet<DossierMeetingPerson> Z_DossierMeetingPersonList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Originator")]
        public EntitySet<DossierTask> Z_DossierTaskList { get;  set; }

        [Field(Nullable = true), FieldMapping("GenderId")]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear)]
        public Gender Z_Gender { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Person")]
        public EntitySet<LastName> Z_LastNameList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Person")]
        public EntitySet<Nationality> Z_NationalityList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Person")]
        public EntitySet<PersonDossierAppointment> Z_PersonDossierAppointmentList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Person")]
        public EntitySet<PersonDossierTask> Z_PersonDossierTaskList { get;  set; }

        [Field(LazyLoad = true)]
        [Association(OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear, PairTo = "Z_Person")]
        public EntitySet<PersonEmploymentSituation> Z_PersonEmploymentSituationList { get;  set; }

        #endregion //END region DataObjectsFields

    }// END partial class
}// END class namespace

As promised above the generated AbstractEntity class with the KeyGenerator for DataObjects.Net.

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Company: Diartis AG
//     Website: www.diartis.ch
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Diartis.KLIB.KLIBDatabase;
using Diartis.KLIB.KLIBLibrary;
using Diartis.KLIB.KLIBLibrary.Debugging;
using Diartis.KLIB.KLIBLibrary.Exceptions;
using Diartis.KLIB.KLIBLibrary.Interfaces;
using Diartis.KLIB.CodeModel.EntityAttributes;
using Xtensive.Storage;
using Xtensive.Storage.Configuration;
using Xtensive.Core.IoC;

namespace Diartis.KLIB.Model
{ // START class namespace
    [Service(typeof(KeyGenerator), Name="AbstractEntity")]
    public class AbstractEntityKeyGenerator : CachingKeyGenerator<int>
    {
        [ServiceConstructor]
        public AbstractEntityKeyGenerator(DomainConfiguration configuration)
            : base(configuration)
        {
        }
    }
    [HierarchyRoot, KeyGenerator(Name="AbstractEntity")]
    public abstract partial class AbstractEntity : Entity 
    { // START partial class
        #region DataObjectsFields

        [Key]
        [Field(Nullable = false), FieldMapping("Id")]
        public int Z_Id { get; private set; }

        [Field(Nullable = false), FieldMapping("CreatedOn")]
        public DateTime Z_CreatedOn { get;  set; }

        [Field(Nullable = false), FieldMapping("CreatedBy")]
        public int Z_CreatedBy { get;  set; }

        [Field(Nullable = false), FieldMapping("ChangedOn")]
        public DateTime Z_ChangedOn { get;  set; }

        [Field(Nullable = false), FieldMapping("ChangedBy")]
        public int Z_ChangedBy { get;  set; }

        [Field(Nullable = false), FieldMapping("IsDefault")]
        public bool Z_IsDefault { get;  set; }

        #endregion //END region DataObjectsFields

    }// END partial class
}// END class namespace

 

I have left out the code for our project since it will make no sense to publish that because it is very specific. You’ll have you own needs in your own project. What you see being generated here is all the attributes needed for DataObjects.Net to be able to create/upgrade the DB. All our properties are generated with the ‘Z_’ prefix. In our specific code we use a new property without this prefix as our model-properties. Here is what is generated for a, lazy loaded, property (Personnel number). The ModelProperty is a class that reacts to changes automatically. In our ViewModel we can bind to this property causing changes in the model to be automatically forwarded.

        protected ModelProperty<string> m_PersonnelNumberP;

        /// <summary>
        /// Gets the PersonnelNumber property. 
        /// </summary>
        // [DebuggerNonUserCode]
        public ModelProperty<string> PersonnelNumberP
        { // START KLIBModel property
            get
            { // START get KLIBModel property
                if (m_PersonnelNumberP == null)
                { // START lazy private property initialization
                    m_PersonnelNumberP = new ModelProperty<string>("PersonnelNumberP", Strings.ToNonNullString(Z_PersonnelNumber), true);
                    UpdateAttributes();
                    m_PersonnelNumberP.ValueChanged += PersonnelNumberPChanged;
                } // END lazy private property initialization
                return m_PersonnelNumberP;
            } // END get KLIBModel property
        } // END KLIBModel property

 

Because we generate partial classes we can extend the model classes by programming in the hand coded class (first example above). Specific code for Person goes into this class and is not overwritten by the generation process.

The ViewModel is built up the same way. We generate code there too.

What this causes is the following:

When we want to change something in the model we only need to do the following:

  • Change the attributes in the hand coded model class.
  • Regenerate the Model and ViewModel with 1 click in Visual Studio.
  • Rebuld the solution.
  • Start the application with ‘Perform’ mode for DataObjects.Net which automatically changes the model in the DB for us (it gets a bit more complex when data has to be manipulated during upgrade, but DataObjects.Net helps us here too with the so called ‘UpgradeHandler’).

That is it.

When we for instance have to add a new entity to the model this is done in minutes. All the structures are immediately available for further programming and the DB is automatically changed by DataObjects.Net.

After the generation process finishes, this is what the Project Explorer looks like in Visual Studio. The T4 script is called ‘Model.tt’:

image

I hope I gave you some insight in how to use code generation in collaboration with DataObjects.Net to create an easily maintained model in your projects.

Reporting in WPF

There are a lot of tools out there that can do some kind of reporting. I’ve tried out many of them and really none of them really suite the needs of the average WPF programmer.

We use the MVVM Pattern for our application and that is great for use with WPF. Of course one does not want to create a complete new tier (the ViewModel) for reporting. The easiest would be a reporting tool that is capable of using the binding mechanisms that are present in WPF.

I stumbled upon the following when searching the Web for reporting tools. http://wpfreports.codeplex.com/

This little project seems to do exactly what I’m looking for. It uses FlowDocuments with DataBinding in XAML to present Data in the DocumentViewer. I really hope that Hans Wolff, who is the author of the tool, keeps at it and creates a powerful library. What would be nice is a Designer. The current implementation (which is a early alpha) does not support that.