ViewData, reflectionbaseret typemapping og performance implikationer

by DotNetNerd 22. March 2009 13:17

Et sted jeg ofte oplever friktion imellem practices og frameworks er imellem godt objektorienteret design og webudviklings frameworks. Veldesignede objekter er sjældent specielt velegnede til f.eks databinding og serialisering. Eksempelvis kaster serialiseringsprocessen i MVC frameworket en exception, hvis man forsøger at serialisere et objekt der er del af en mange til mange relation (hvor to klasser har collections indeholdende instancer af hinandens typer). Det giver fint mening at det forholder sig sådan i en serialiseringskontekst, men omvendt kan man ikke i sit design undvære mange-til-mange relationer. I forhold til databinding kan det være et issue at man gerne vil databinde et objekt hvor nogle properties er komplekse typer, hvorfra der skal noget logik til at afgøre hvilken værdi der skal representere objektet i brugerfladen.

Løsningen på den slags udfordringer er ofte at lave en ViewData klasse der som udgangspunkt er et subset af den oprindelige klasse. Properties som er mere komplekse kan så fjernes eller ændres, og udregnede eller relaterede data kan tilføjes viewet. Som bekendt er der ingen designbeslutninger uden konsekvenser, og her er den tydelige konsekvens at man nu skal skrive kode til at mappe imellem entiteter og viewdata. I mange tilfælde er det virkelig trivielt, da properties navngives ens, så for at undgå at skulle gøre dette manuelt skrev jeg en MapToView metode.

public static ViewType MapToView<EntityType, ViewType>(EntityType entity) where ViewType : new()
{
    var view = new ViewType();

    var entityType = entity.GetType();
    var viewType = view.GetType();

    foreach(var property in entityType.GetProperties())
    {
        var viewProp = viewType.GetProperty(property.Name);
        if(viewProp != null && viewProp.PropertyType == property.PropertyType)
        {
            viewProp.SetValue(view, property.GetValue(entity, null), null);
        }
    }

    return view;
}

Den er naturligvis reflection baseret, og så siger ens barnelærdom jo at man skal passe på hvad det betyder for performance. En helt kort test hvor jeg har to klasser med 10 string properties som jeg mapper 1000 gange viste over 5 observationer at det tager ca. 53 gange så lang tid med MapToView frem for med manuel mapning. Det skal dog ses i det lys at vi snakker omkring 145 millisekunder på en 3,2 GHz Pentium 4 for de 1000 mapninger - så med mindre der virkelig er skarpe designkrav omkring performance vil det næppe være et issue.

Tags:

Comments (3) -

Martin Jul
Martin Jul Denmark
3/24/2009 11:51:28 PM #

Fin idé. Jeg har for nylig arbejdet med et team i en virksomhed, der kunne have glæde af at lave view-klasser at binde til og dit convention-over-configuration approach er rigtigt godt.

Jeg performance-tunede noget lignende reflection-baseret kode for nogle år siden, og med mindre .NET-runtimen er blevet ændret, så var nøglen til bedre performance at cache værdierne af nogle af Reflection-opslagene.

Alternativt kan man code-emit'e mapper-klasserne, enten compile-time eller runtime, hvilket bringer mapperne on-par med håndskrevne ditto uden at koste noget særligt ekstra arbejde.

mvh
Martin

Martin Jul
Martin Jul Denmark
3/25/2009 12:13:30 AM #

Bemærk forøvrigt, at ovenstående ViewData løsning giver anledning til det problem, at man nu er tvunget til at til at synkronisere navne i begge klasser (Entity og View), hvilket bryder med grundlæggende OO-principper om løs kobling og indkapsing, og giver en masse ballade i forhold til f.eks. refactoring. Det kan ikke anbefales.

DotNetNerd
DotNetNerd Denmark
3/26/2009 9:05:45 PM #

Der er helt sikkert mange måder at performancetune på - alt efter ens scenarie. I mange scenarier er de par milisekunder vi snakker om her dog ikke værd at kaste tid efter at barbere væk. At generere koden er selvfølgelig et andet godt approach - men hver løsning giver selvfølgelig sine problemer, og herved slipper man ikke for at have mapningskoden.

Jeg vil sige at nogen gange er principper til for at blive brudt, og i forhold til views i MVC synes jeg indimellem den sundeste beslutning virker til at bryde med DRY princippet. Hvilket er paradoksalt da det er et af de mest omtalte principper i forbindelse med MVC.

Hvis man nægter at have dupletter i views og klasser ender man desværre hurtigt med at skulle leve med nogle ubehagelige konsekvenser:

1) Ens views vil indeholder rigtigt mange if-sætninger og bliver vanskelige at vedligeholde/forstå.
2) Stronglytyped views, skal ofte have et subset af properties fra forskellige objekter, kombineret med udregnede værdier o.lign. Hvis properties ikke dupleres er man nød til at sende alt med Frown, og man vil få et issue med hvad man skal gøre af de værdier man ellers skal have med som ikke er på entiteten. At lave et viewobjekt med entiteten som property kan nogen gange løse det, men der har også sine konsekvenser.

Who am I?

My name is Christian Holm Diget, and I work as an independent consultant, in Denmark, where I write code, give advice on architecture and help with training. On the side I get to do a bit of speaking and help with miscellaneous community events.

Some of my primary focus areas are code quality, programming languages and using new technologies to provide value.

Microsoft Certified Professional Developer

Microsoft Most Valuable Professional

Month List