Web projects I worked on

Wageningen Universiteit & Research

100 year anniversary of the Wageningen University LEB department

A Google maps site showing all the activity of the LEB of the Wageningen University

image

Pachtprijzen

A webpage showing the ‘Pachtprijs gebieden’ in the Netherlands using Leaflet

image

Presentations2Go Scheduler

Presentations2Go released version 5 and I’m responsible for migrating the Scheduler from version 4 to this version. Below you see the Encoder status screen.

image

Course Registration System

For this application I’ve solved many bugs.

The startscreen for the PET School

image

The start screen for the ESD School

image

My experiences developing a native App with C#, Xamarin, MvvmCross and Visual Studio 2017 for Windows.

Introduction

I have over 35 years of experience in programming on environments varying from mainframe, midrange, small systems to PC’s but never have I met so many problems in one development process.

The past 6 months I’ve been developing just 1 app for Android and iOS and it has been an experience that gives me mixed feelings about mobile development. Don’t get me wrong I just love programming in general but this… this was different.

The app

This is what the app is supposed to do:

  • Register the device with UserId and Password via a webservice.
  • Enter a TAN once for the registration and verify it via a webservice. This authenticates the device with the server just once.
  • Add an Application (website) one wishes to verify. There are 5 websites notifications can be received for.
  • Accept push notifications from the server via Firebase when a user wishes to authenticate for a website.
  • When a notification is received:
    • When received from a webbrowser on a PC start a QR code scanner and scan the code that is shown on the webpage. When the information in the QR code equals the information in the notification accept the verification.
    • When received from a mobile device show buttons ‘Accept’ and ‘Cancel’.
      • When ‘Accept’ is clicked tell the server the authentication is accepted via a webservice.
      • When ‘Cancel’ is clicked tell the server the authentication is not accepted via a webservice.
  • The user must be able to add more websites to the registration.
  • The user must be able to remove websites from the registration.

Development choices

It was decided that the app would be built with MvvmCross with native code and not Xamarin.Forms A consultant from a external softwarehouse told us that as long as you stay with the controls provided you’re ok but when you need something extra you would have to write your own renderers which is a pain. And of course he knew MvvmCross very well. For the record, I had more experience with XAML at that time and favoured Forms. Also bad experiences with third party UI controls had influence on the decisions made.

It was also decided we would use PCL for the Core functionality. I would have liked to go with .Net Standard but that was not covering all needed functionality at that time.

MvvmCross forces you into the MVVM pattern which is a good thing. You have to think about reusability of code all the time and separate generic functionality and device specific code as much as possible.

Learning curve

Learning MvvmCross was a first. It’s a good concept and overall works pretty smooth. The MVVM pattern was not new to me which gave me a headstart. The difficulty with PCL in combination with native code is the  communication between the libraries. You can access PCL from native but not (easily) from PCL to native. MvvmCross bridges that gap easily using Inversion of Control (as Forms would have done). With the help of the consultant MvvmCross was learned quickly. It does however have some perks. Because of late binding the linker sometimes does not know that you’re using certain functionality and does not add it to the endresult which leads to confusing situations. But once you get the hang of it you’ll recognize these situations pretty fast. A pitfall is debug and release mode. In debug mode the linker adds everything all the time but it does not do so in release mode causing the app not to work correctly in it’s final release.

Practice

Because Xamarin is under constant development you’re on the bleeding edge and because you have to conform to two (or more) different platforms you better be prepared to meet frequent crashes and hangs, wrong builds without any clues why, slow performing  builds, frustrating provisioning issues with Apple’s paranoid and absurd complex protection system in combination with wrongly cashed information, differences in layout, OS versions, screensizes of the individual devices you’re building for, etc.

Documentation is sparse, incomplete or deprecated and very often not applicable for your development choices. A lot of the examples come from websites like Stackoverflow but are for a plethora of applicable development choices. You can have examples for Swift, Objective-C and C# for Apple in combination with explanations for Xamarin Studio, XCode, Visual Studio on PC or on Mac. Very often you can deduce from the Swift and Objective-C examples what it should be in C# because luckily the interfaces written very often follow the same name conventions but sometimes it’s not clear or just plain difficult. Without the community development would have been very very difficult if not impossible. Solving everything on your own is very time consuming and sometimes very frustrating.

Using the Xamarin designers needs a lot of getting used to. I really had trouble understanding the constraint system used on iOS devices. I’ve been tearing my hair out why a design was shown correctly in the designer but not on the simulator or device. I finally got the hang of it but It’s been a struggle. The designers are not stable at all. For XAML I was able to grasp the syntax in XML quite quick but the XML of iOS views is hugely complex and I still haven’t got that in my skillset so I rely on the designer heavily. The XML of Android views is quite concise and easy to understand so I hardly ever use that designer. For data binding Android views with MvvmCross you have to edit the axml by hand (at least I’ve not found a way via the designer yet).

Bugs, bugs, bugs

Sometimes development progress comes to a grinding halt when again some kind of problem with the development environment crops up. I’ve had hangs during solution loads, during debugging and deployment. Sometimes deployment becomes impossible and you’re stuck being unable to test your app on a device. One day, because I was using my own Samsung S6, I really had to factory reset my it to be able to deploy to it again and I was forced to completely setup it up again (sigh..). Emulators (or simulators as Apple calls them) are a good solution for straightforward development and deployment to them is pretty fast. But not all functionality a real device can do can be done on an emulator. For instance Firebase is not supported in emulators. It is therefore imperative your final tests are on a device. This also causes difficulty. Who of us has all the variations in devices being used in the world? We use an iPad Pro for iOS and a Motorola G Play for Android testing. It has iOS 10 but we do not have a iOS 9 device. Some code is specific for these devices (Firebase) so how do we test that? Xamarin offers a service with lot’s of devices but that is pretty expensive to use.

You can be faced with the situation that the day before everything was working perfectly and the next day (after a fresh boot) everything seems broken. Builds just aren’t stable at the moment. Sometimes you end up with the situation having to clean up and rebuild your solution. Sometimes even that does not help and you have to manually remove all ‘obj’ and ‘bin’ directories just to get going again. In my experience changing something in PCL will cause wrong builds so you better be cleaning and rebuilding when changing something in PCL. And it takes a lot of time.

Testing

Turnaround times are just plain bad. Every build and deployment using a fast computer with a SSD and lot’s of memory still takes at least up to three minutes for a complete build(depending on the size of your solution of course) meaning that the a small change can take a relatively long time before it can be tested. For iOS development you have to have a Mac. We use a 2012 refurbished Macbook and it is pretty fast. All communication goes through a network connection and the Mac only has a 100Mbit port. Even designing a view in the Xamarin designer causes a generation process to start on the Mac. Although this is a bottleneck builds on a Mac are faster than the local builds for Android. Deploying to a real device is slower than deploying to an emulator for both iOS and Android. Frustrating is the fact that when you loose connection to the Mac everything just stops working. I very often loose connection and reconnect does not always work. You’ll have to restart Visual Studio to be able to reconnect.

Debugging

Debugging is pretty good. There are limitations however. You can not edit and continue and you can not change the position of the current execution point. Because you’re testing against native code you’ll very often see that you’re in external code rather than in your own code when an exception occurs but that is not uncommon in other development too. The logs on the devices help a lot during debugging. You will have to filter out logging for your app because when you run on a device with a lot of installed apps logs can become very bulky. Stacktraces are very often native too (Java on Android and Objective-C on iOS). You’ll have to learn how to interpret these but most of the time it is pretty clear although I’ve seen some pretty confusing errors being thrown that put you off track. To quote a very famous Britisch detective: “If you can’t find the solution in the explainable investigate the unexplained.”.

Conclusion

Mobile development with Xamarin is time consuming and thus expensive. The platform, although it has been around quite a long time, is not stable. Because of the many different Development environments, Platforms and Programming possibilities the amount of knowledge and skills needed is huge making the learning curve pretty steep. Documentation is sparse, incomplete and often deprecated. The community is huge and very active. Before deciding about Development environment and architecture (Forms or Native, MvvmCross, PCL or .Net Standard) one should investigate the (im)possibilities very thorough. Once you have made you choices and head on with development it is hard to change.

DataObjects.Net code T4 generation

DataObjects.Net is a very versatile Object Relational Mapper (ORM). What an ORM does is solve the relation object-oriented clash we developers always have when we program in OO languages like C#, Java, C++, etc in combination with a Relational Database like f.i. Microsoft SQL Server. After careful deliberation we decided to use DataObjects.Net (DO) as our ORM.

We also wanted to use T4 Code Generation for our ViewModel and extension of the Data Model. Now DO does not support T4 out of the box. In a previous project we wrote a library that uses reflection to dissect the compiled Assembly produced by the compiler in combination with Postsharp (used by DO to inject code).

Now in DO, which is primarily a ‘code first’ tool, you define your Data Model in code using Attribute classes. Here’s an example.

using Xtensive.Orm;

namespace T4DB.Entities
{
    [HierarchyRoot, KeyGenerator(Name = "Root")]
    [TableMapping("Root")]
    public class RootEntity : Entity
    {
        [Key]
        [Field(Nullable = false)]
        public int Id { get; private set; }
    }
}

using Xtensive.Orm;

namespace T4DB.Entities
{
    [TableMapping("Person")]
    public class PersonEntity : RootEntity
    {
        [Field(Nullable = false)]
        public string FirstName { get; set; }

        [Field(Nullable = false)]
        public string LastName { get; set; }

        [Field(Nullable = false)]
        public string Prefix { get; set; }
    }
}

As you can see we use a RootEntity that is inherited by, in this case, the PersonEntity. We use this construct thoughout the DataModel. What this causes is that each entity has its own unique key throughout all the data (no row in a table has the same key). This is a unique feature of DO and gives us the ability to find entities just by its key. DO takes care of the resolution into the right type for us. Ok so far this little side-step.

After compiling the above Data Model you get an Assembly. An Assembly is a written into a DLL stored on disk. In the case above the name will be ‘My.Model.dll’.

What is present in this Assembly we are able to dissect using reflection. Now we could reflect all the information we need in T4 but that would clutter the templates so we decided to build a second Assembly that is going to gather all the needed information for code generation.

Here’s an example showing the loading of the Data Model Assembly into a Metadata instance in a T4 template.

<#@ output extension=".cs" #>
<#@ Include file="$(SolutionDir)Common.T4\Assemblies.ttinclude" #><#   
    IServiceProvider hostServiceProvider = (IServiceProvider)Host;

    string directoryPath = Host.ResolvePath(@"..\..\My.Model");
    string configName = dte.Solution.SolutionBuild.ActiveConfiguration.Name;
    string assemblyName = directoryPath + @"\bin\Debug\My.Model.dll";

    Assembly assembly = Assembly.LoadFrom(assemblyName);
    Metadata.MetadataContainer.AddAssembly(assembly);
#>

Now of cource we need the Metadata class. Here is the part of that class that loads the assembly from disk into the MetadataContainer using the AddAssembly() method.

using System;
using System.Linq;
using System.Collections.Generic;
using Xtensive.Orm;
using System.Reflection;
using System.Windows.Forms;

namespace My.Metadata
{
    /// <summary>
    /// Metadata container class.
    /// </summary>
    public class Metadata
    {
        private static Metadata m_MetadataContainer;

        /// <summary>
        /// Singleton for obtaining a MetadataContainer object
        /// </summary>
        public static Metadata MetadataContainer
        {
            get
            {
                if (m_MetadataContainer == null)
                {
                    m_MetadataContainer = new Metadata();
                }

                return m_MetadataContainer;
            }
        }
        
        /// <summary>
        /// Add all DO.Net Entity types of an Assembly
        /// </summary>
        public void AddAssembly(Assembly assembly)
        {
            try
            {
                foreach (var entity in assembly.GetTypes())
                {
                    AddEntity(entity);
                }
            }
            catch (ReflectionTypeLoadException lex)
            {
                int hr = System.Runtime.InteropServices.Marshal.GetHRForException(lex);
                MessageBox.Show(lex.LoaderExceptions[0].Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private List<EntityMetadata> m_Entities;

        /// <summary>
        /// List of Entities in the metadata collection.
        /// </summary>
        public List<EntityMetadata> Entities
        {
            get
            {
                if (m_Entities == null)
                {
                    m_Entities = new List<EntityMetadata>();
                }

                return m_Entities;
            }
        }

        /// <summary>
        /// Add en entity type to the container.
        /// </summary>
        private void AddEntity(Type entity)
        {
            if (!ContainsEntity(entity))
            {
                if (entity.IsSubclassOf(typeof(Entity)))
                {
                    Entities.Add(new EntityMetadata(entity));
                }
            }
        }

        /// <summary>
        /// Is the type already present in the container?
        /// </summary>
        private bool ContainsEntity(Type type)
        {
            foreach (EntityMetadata entityWrapper in Entities)
            {
                if (entityWrapper.Type == type)
                {
                    return true;
                }
            }

            return false;
        }
    }
}

Note: This class may not compile correctly because I combined several snippets from the original code. You can find the complete Metadata class in the attached zip file.

In the AddAssembly() you can see we add all DO entities (the AddEntity adds type Entity only) using the EntityMetadata class.

After loading the Assembly in the Metadata object we have all the DataObject Entities in a collection. Now each of the above mentioned EntityMetadata collects all the member information from the Entity. So after loading the Assembly we have an Entities collection and per Entity a Properties collection through which we can iterate in our template. Below is the MyFirstTemplate.tt taken from the attached zip file.

<#@ output extension=".txt" #>
<#@ Include file="$(SolutionDir)T4Includes\Assemblies.ttinclude" #><#   
    try
    {    // START main try
        // Initialization Output Manager
        Manager outputManager = Manager.Create(Host, GenerationEnvironment);

        // Start Costruction CodeModelTree
        IServiceProvider hostServiceProvider = (IServiceProvider)Host;
        DTE2 dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.11.0");

        string directoryPath = Host.ResolvePath(@"..\My.Model");
        string assemblyName = directoryPath + @"\bin\Debug\My.Model.dll";
        Assembly assembly = Assembly.LoadFrom(assemblyName);
        Metadata.MetadataContainer.AddAssembly(assembly);
#>
// Empty file
<#
        try
        {
            foreach (EntityMetadata entity in Metadata.MetadataContainer.Entities)
            {
                if(entity != null)
                {
                    outputManager.StartNewFile(entity.VMName + ".generated.cs");

#>
using System;

namespace MyNamespace
{
    public partial class <#= entity.Name #>
    {
        public <#= entity.Name #>()
        {
        }
<#
                    foreach (var property in entity.Properties)
                    {
#>
        public <#= property.FieldTypeString #> <#= property.Name #> { get; set; }
<#
                    }
                }
#>
    }
}
<#
            }
        }
        catch (Exception ex)
        {
#>
            // ERROR <#= ex.Message #> 
            // STACK <#= ex.StackTrace #>
<#
        }

        outputManager.Process(true); //write files to disk

    } // END main try
    catch(Exception ex)
    {
        MessageBox.Show("Error in MyFirstTemplate.tt: " + Environment.NewLine + Environment.NewLine + ex.ToString(),"Error in Transformation");
    }
#>
<#+  
#>

After saving this file to disk the template generator will start loading the template and saves the generated output to disk and adds it to the project as subitems from the template.

image

In for instance the VMPersonEntity.generated.cs the following is generated with this template.

using System;

namespace MyNamespace
{
    public partial class PersonEntity
    {
        public PersonEntity()
        {
        }
        public String FirstName { get; set; }
        public String LastName { get; set; }
        public String Prefix { get; set; }
    }
}

Solution: T4.zip

PS: In order to be able to compile this solution you will need to add the DataObjects.Net Nuget package via the Nuget Package Manager.

image

Type ‘Dataobjects’ in the searchbox in the upper right corner and wait for the result.

Click the ‘Install’ button.

image

Select the following projects for installation.

image

After that start the compilation. It is possible the version of DO and PostSharp is different in your new installation. In that case you will have to change the version info in the ‘Assemblies.ttinclude’ file in the T4Include project.

Also remove the RequiresPostSharp.cs files where not needed. That file is only needed in the T4Database project.

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

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.