Placer dine Entity Framework Migrationsfiler i en alternativ mappe

Jeg har i min anvendelse af Entity Framework haft behov for at placere mine migrationsfiler et andet sted end de, som standard, bliver placeret.

Dette kan gøres i constructoren af DbMigrationsConfiguration (Configurations.cs). Denne fil bliver genereret, når man slår migrations til (Enable-Migrations i PMC).

Egenskaben hedder MigrationsDirectory og skal sættes til en relativ sti ift. den DLL, som migrations ligger i. Følgende er et eksempel på dette:

internal sealed class Configuration 
    : DbMigrationsConfiguration<ShopContext>
{
    public Configuration()
    {
        MigrationsDirectory = "Ef\\Migrations";
        MigrationsNamespace = "Shop.Repositories.Ef.Migrations";
    }
}

I ovenstående har jeg dels, med MigrationsDirectory, sat mappen migrationfiler skrives til, dels, med MigrationsNamespace, det namespace, som nye migrations anvender. Det kan være relevant, at namespace følger placeringen af de fysiske filer og det har man, som her demonstreret, mulighed for også at styre.

Gem afledte egenskaber i databasen med Entity Framework

Jeg sidder og arbejder med CodeFirst i Entity Framework 6 (EF6) og det er sådan set også helt fint. Jeg dog hidtil haft en lille udfordring med, at beregnede egenskaber ikke rigtig kom med ned i databasen og jeg dermed skulle have indlæst objektet i hukommelsen for at kunne arbejde med disse egenskaber.

Det er i mere komplekse tilfælde nok også det rigtige at gøre, men for nogle typer data, kan det være rigtig praktisk, at få den beregnede værdi gemt i databasen, så der kan foretages forespørgsler på disse egenskaber direkte i databasen, uden man skal have dem ind i hukommelsen.

Hidtil har et objekt haft nogenlunde flg. struktur:

public class Kamp
{
    public List<Periode> Perioder { get; set; }

    public DateTime Startdato { get; set; }
    public DateTime Slutdato
    {
        get
        {
            return 
                Startdato.AddTicks(
                    Perioder.Sum(p => p.Loebetid.Ticks));
        }
    }
}

public class Periode
{
    public TimeSpan Loebetid { get; set; }
}

Her har jeg en Kamp som kan have flere perioder. Hver periode har en løbetid på et givet tidsinterval. Slutdatoen bliver derfor summen af løbetider for alle perioder. Hvis jeg ændrer løbetiden på en periode (via objektet af typen Kamp), vil slutdatoen ændre sig automatisk, hvilket jo er rigtig rart!

Nu vil jeg imidlertid gerne finde alle de kampe som er igang, dvs. den aktuelle dato ligger mellem startdatoen og slutdatoen. For at kunne opfylde dette krav er jeg med den aktuelle struktur, nød til at indlæse alle kampe, hvor den aktuelle dato er større end eller lig med startdatoen og derefter gennemløbe alle kampe (i hukommelsen) for at se, om slutdatoen er overskredet. Det er måske ikke det store problem, hvis man har 100 kampe, men hvis man har en mio eller flere, har jeg på fornemmelsen, at det bliver en ret tung omgang. Desuden vil det med tiden blive et større og større problem, da flere og flere kampes startdato vil ligge i fortiden og dermed blive indlæst i denne forspørgsel. Der må altså tænkes lidt alternativt...

En løsningsmulighed

Gem slutdatoen uden at gøre den til simpel egenskab med get og set. Dette gøres ved at lade slutdatoen bibeholde sin getter og så tilføje en tom setter. På den måde vil EF generere egenskaben i databasen og gemme den værdi som getteren genererer, når objektet gemmes.

Når objektet indlæses igen, vil værdien bare blive skrottet, da slutdatoen jo beregnes på grundlag af de indlæste perioder (husk at "Include" Perioder i din query!).

Kamp kommer til at se således ud i stedet:

public class Kamp
{
    public List<Periode> Perioder { get; set; }

    public DateTime Startdato { get; set; }
    public DateTime Slutdato
    {
        get
        {
            return 
                Startdato.AddTicks(
                    Perioder.Sum(p => p.Loebetid.Ticks));
        }
        set {}
    }
}

Kører du nu Add-Migration fra PMC (Package Manager Console) i VS, vil der blive genereret et felt til slutdato i Kamp-tabellen. Dette felt kan nu anvendes til at foretage forespørgsler på, for den gemte værdi er den beregnede slutdato på det tidspunkt, hvor Kampen sidst blev gemt. Det kunne f.eks. se således ud i LINQ to EF:

using(var ctx = new DataContext())
{
    var igangvaerendeKampe =
        ctx.Kampe
           .Include(k => k.Perioder)
           .Where(k => k.Startdato <= DateTime.Now
                       && k.Slutdato >= DateTime.Now)
           .ToList();

     return igangvaerendeKampe;
}

Med ovenstående har du filtreret kampene i databasen og nok gjort noget godt for dit systems performance og skalerbarhed i fremtiden...

Konklusion

Alt efter ens krav og behov til funktionalitet, kan det godt betale sig, at gå lidt på kompromis med de strikse normaliseringskrave der eller bør herske ifm. databasedesign. I dette tilfælde vil jeg gætte på, at der på sigt er vundet en del performance og dermed et problem mindre afværget - inden det opstod.

Hvordan laver man geolokation

Jeg står overfor at skulle arbejde med geolokation på en hjemmeside, nærmere bestemt finde ud af hvormange lokationer der findes indenfor en radius af et givet punkt.

Det har jeg ikke arbejdet med før, men jeg fornemmer det er lettere end det har været, da der dels er nogle gode resurser omkring emner, dels er nogle API'er man kan anvende til udføre noget af arbejdet.

Opgaven er såmænd ret enkel, nemlig "find alle lokationer, som er indenfor en radius på X km fra min aktuelle lokation".

Det involverer umiddelbart flg.

  1. at jeg kender eller kan finde den aktuelle lokation
  2. at jeg kender lokationen på alle de ting der skal findes
  3. at jeg, på en nem måde, kan foretage et opslag på disse oplysninger

Find aktuelle brugers lokation

Det første kan jeg i nogen udstrækning bruge browserens faciliteter til. Nyere browsere har indbygget en eller anden grad af lokationsbestemmelse og den tager jeg udgangspunkt i.

Det kunne f.eks. se således ud:

window.navigator.geolocation.getCurrentPosition(function(pos) { 
    var lat = pos.coords.latitude;
    var lon = pos.coords.longitude;
})

Find lokation på adresser

Det andet kan jeg (nok) bruge Google's Geocoding API til. Det skal bruges på den måde, at jeg vha. en adresse omsætter denne til et sæt længde- og breddegrader. Disse gemmer jeg  sammen med det der skal findes.

Dette API kræver, at jeg opretter en API-nøgle før jeg kan foretage opslag (se instruktionerne på siden til Geocoding API'et), men når det så er gjort, kan jeg spørge på en adresse med et kald til flg. url:

var url = "https://maps.googleapis.com/maps/api/geocode/json?"
    + "address=Hovedgaden+12,8000+Århus&key=[API-nøglem]";

Udtræk lokationer der matcher området

Det tredje kan jeg, med udgangspunkt i jeg anvender SQL Server, gøre ved et fancy opslag, hvor jeg får returneret et antal rækker, som ligger indenfor et givet cirkulært område. Hvordan der foretages en sortering efter nærhed, har jeg ikke gennemskuet, men med noget dataanalyse kan man måske nå frem til et approximeret sorteret resultet - måske endda et reelt sorteret resultat. Hvem ved...? Mit umiddelbare behov er dog ikke, at resultatet er sorteret efter nærhed, så det spekulerer jeg ikke så meget i lige nu.

Anyway! For at udtrække de lokationer som ligger indenfor en given radius (på grundlag af længde- og breddegrader), kan jeg så bruge denne SQL-konstruktion:

declare 
   @latStartingPoint float, 
   @lngStartingPoint float, 
   @distKmFromStartingPoint float;

set @latStartingPoint = 55.0
set @lngStartingPoint = 9.0
set @distKmFromStartingPoint = 13.0

select * 
from testLocation 
where ( 
   6371 * 
   acos( 
       cos( radians(@latStartingPoint) ) * 
       cos( radians( lat ) ) * 
       cos( radians( lng ) - radians(@lngStartingPoint) ) + 
       sin( radians(@latStartingPoint) ) * 
       sin( radians( lat ) ) 
   )) < @distKmFromStartingPoint

Jeg har erklæret 3 variable i SQL'en ovenfor, hvor

  • @latFromStartingPoint er breddegraden for udgangspunktet
  • @lngFromStartingPoint er længdegraden for udgangspunkt
  • @distKmFromStartingPoint er antal km fra center der ønskes resultater fra
  • Det magiske tal 6371, er jordens radius

Konklusion

Som det kan ses, er det i teorien, rimeligt ligetil... så må vi se om det også er i praksis :-)

 

Datastyrede routes i ASP.NET MVC

I disse tider med større og større fokus på SEO-venlighed, kan det være rart, hvis man kan basere sine url'er på data fra eksempelvis sin database.

Man kunne have en produktdatabase, hvor produktet har en unik (url-venlig) streng, som kan anvendes til at finde produktet. I et setup uden nogen specielle regler, kan dette være en udfordring at få gjort stømlinet. Med en routedefinition som denne:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { 
        controller = "Home", 
        action = "Index", 
        id = UrlParameter.Optional 
    }
);

hvor produkt-controlleren hedder ProduktController, action-metoden til at vise et produkt hedder Vis og produktets urlnavn ligger i id-parameteren, kunne url'en til et produkt se således ud:

/produkt/vis/stor-tallerken-med-sort-kant

Det kan være fint nok ift. SEO, men det ville jo være rart, hvis man kunne slippe for de lidt overflødige controller- og action-tekster, så url'en kom til at se således ud i stedet:

/stor-tallerken-med-sort-kant

Dette kan opnåes med en RouteConstraint og en wildcard-urlparameter. Route-mapping kunne derfor se således ud:

routes.MapRoute(
    name: "Default",
    url: "{id}",
    defaults: new { controller = "Produkt", action = "Vis" },
    constraints: new { id = new ProduktRouteConstraint() }
);

Nu kaldes ProduktRouteConstraint's Match-metode hver gang routing kaldes. Denne Match-metode skal så håndtere et opslag i produktdatabasen for at se, om den angivne id refererer til et eksisterende produkt. Match-metoden returnerer en bool, der indikerer om den aktuelle route skal anvendes eller ej, dvs. om produktet findes i dette tilfælde. En implementering kunne se således ud:

public class ProduktRouteConstraint : IRouteConstraint
{
    public bool Match(
            HttpContextBase httpContext,
            Route route,
            string parameterName,
            RouteValueDictionary values,
            RouteDirection routeDirection
        )
    {
        var svc = ProduktService();
        var id = values["id"].ToString();
        return svc.FindesProduktMedUrlnavn(id);
    }
}

Match-metoden fyrer op under en produktservice-klasse, der, via metoden FindesProduktMedUrlnavn, kan svare på, om produktet med det angivne navn (fra url'en) findes eller ej.

Hvis url'en ikke er et match, fortsætter routing-systemet videre til næste regel (som det altid har gjort). Derfor skal denne regel helst ikke være den sidste regel i rækken af routing-regler. 

En ting man skal være opmærksom på med denne metode er, at routing-systemet aktiveres hver gang der foretages et request til sitet. Derfor skal routingconstraints helst være så optimerede som muligt, da sitet ellers vil virke langsomt. Det vil med andre ord være en god idé, at overveje caching af grundlaget for disse specielle routingregler, i dette eksempel produktets urlnavne.

e-handel og kundens rettigheder

Jeg sidder lige og arbejder med en webshop, som handler med fysiske varer og i den forbindelse har der været behov for at finde ud af, hvilke rettigheder og pligter kunden hhv. butikken har.

I den forbindelse er følgende links fundet relevante og hjælpsomme i forståelsen af disse emner:

Kundens fortrydelsesret

Tilfælde hvor der ikke er fortrydelsesret

Forretningens oplysningspligt før salg

Generelt omkring net-handel

Alle ovenstående links kommer fra samme side og er en del af samme "kompendie", men udvalgte links er medtaget her, da mit fokus har være på handelsbetingelser og fortrydelsesret.

Manglende intellisence i Visual Studio 2013

Jeg sad idag og boksede med et problem i Visual Studio, hvor jeg ikke kunne få intellisence til at fungere. Dette til trods for, at jeg godt kunne complie projektet og køre det i IIS Express.

Der var røde bølgestreger under et namespace i using-sektionen og en type kunne ikke findes. Da projektet kunne compile, tænkte jeg det måtte have noget med udviklingsmiljøet at gøre.

Jeg havde lavet en cleanup og forsøgt at køre "rebuild" på hele min solution, men lige lidt hjalp det. 

Jeg søgte på problemet og fandt dette link: https://stackoverflow.com/questions/21471887/visual-studio-2013-intellisense-stops-working-for-asp-net-mvc5-controllers/23983883#23983883

Løsningen var, i mit tilfælde, helt enkelt, at lukke VS 2013 og slette filen <projektnavnet>.v12.suo og starte VS 2013 op igen.

CSV parser til .net projekter

Det sker at man falder over en opgave, som kan løses ved at importere data fra en CSV-fil (kommaseparerede data) og i den forbindelse kunne man jo strikke sin egen parser sammen.

CSV-formatet er dog lidt mere tricky end man lige umiddelbart skulle forestille sig, så derfor bør man nok kigge på markedet, om der ikke findes en anden som allerede har strikket noget sammen (og som er testet grundigt igennem af mange andre).

Det var netop hvad jeg gjorde. Jeg fandt frem til denne på CodeProject: http://www.codeproject.com/Articles/9258/A-Fast-CSV-Reader

Den ser ud til at fungere fint og kan installeres i et .NET-projekt vi NuGet, så det bliver vist ikke ret meget lettere at komme igang. Oveni det er den ret simpel at anvende.

Entity Framework downgrade

Jeg har lige været lidt eventyrlysten og opgraderet et MVC 4 projekt fra EF5 til EF6. Efter det havde jeg lidt problemer med at få mit site til at køre på serveren (efter web deploy og auto migrations på serveren).

Fint nok, så nedgraderer jeg bare til EF5 igen og så burde den jo være i vinkel... men ak, så let skulle det ikke gå. Flg. fejl opstod når jeg prøvede at publisere:

Cannot insert the value NULL into column 'ContextKey', table 'ScabsContext.dbo.__MigrationHistory'; column does not allow nulls. INSERT fails. The statement has been terminated.

OK, nu er jeg ikke 100 meter mester udi tabelstrukturen på MigrationHistory, så jeg tænte der måtte være en uoverensstemmelse mellem EF-versionerne i de forskellige komponenter i min solution, så jeg sikrede mig, at alle versioner var ens. Lige lidt hjalp det.

Hmm... Google! Kommer op med et Stack Overflow-indlæg, som omhandler dette emne (SO styrer for vildt!).

Det viser sig, at EF6 tilføjer en ny kolonne i MigrationHistory, som hedder (trommehvirvel!) - ContextKey.

Denne ContextKey bliver jo ikke fjernet fra tabellen igen, bare fordi man nedgraderer fra EF6 til EF5, så når en migration køres fra EF5 mod en EF6 MigrationHistory... ja, så får du førnævnte fejl.

Dette kan løses ved at udføre flg. ændringer på MigrationHistory-tabellen:

ALTER TABLE dbo.__MigrationHistory DROP CONSTRAINT [PK_dbo.__MigrationHistory2]

ALTER TABLE dbo.__MigrationHistory DROP COLUMN ContextKey

ALTER TABLE dbo.__MigrationHistory ADD CONSTRAINT [PK_dbo.__MigrationHistory] PRIMARY KEY (MigrationId)

ALTER TABLE dbo.__MigrationHistory ALTER COLUMN MigrationId NVARCHAR(255) NOT NULL

Så skulle den tabel vist være tilbage i EF5-format!
 

C# out og ref-parametre

Har man brug for at få værdier ud af en funktion kan man naturligvis bruge funktionens returværdi, hvilket er oplagt i de fleste tilfælde. Der kan dog være tilfælde, hvor man har brug for at returnere flere værdier fra en funktion og i disse tilfælde har man et par yderligere muligheder.

out-parametre

Med out-parameteren kan man sende en værdi ud af et funktionskald. Parameteren SKAL tildeles en værdi inde i funktionen, ellers får man en compilefejl. Parameteren er KUN til output, evt. værdi i input vil blive ignoreret. Følgende vil give en compilefejl (fordi p endnu ikke er tildelt en værdi inde i fn, inden den refereres):

void fn(out int p)
{
    return p + 1;
}
var x = 1;
fn(out x);

Samtidig opfattes denne parameter alene som en output-parameter, dvs. den værdi parameteren evt. måtte have i kaldet, kan ikke bruges i funktionen. Følgende vil f.eks. give en kørselsfejl, fordi parameteren p ikke er tildelt en værdi inden den bruges (inde i funktionen):

void fn(out int p)
{
    Console.WriteLine(p);
    p = 2;
}
var x = 10;
fn(out x);

ref-parametre

Med ref-parametre kan man sende værdier ind i en funktion og samtidig give funktionen mulighed for at ændre værdien af den parametere (så den også ændres i den kaldende parts variabel). Dette kunne se således ud:

void fn(ref int p)
{
    p++;
}
var x = 1;
fn(ref x);
// x er nu 2;

Denne måde at bruge ref på, kan være "farlig" og betragtes af mange som dårlig kodestil. Begrundelsen er, at kaldet til fn har en skjult sideeffekt på den kaldende parts kode. Denne sideeffekt er svær at forudse, hvis man ikke lige har adgang til koden i fn og kan se hvad der sker. Selvom man havde adgang til koden, var det ikke nødvendigvis let at gennemskue denne sideeffekt alligevel. Det er derfor ikke tilrådeligt, at benytte ref på denne måde.

Når ref-modifikatoren bruges er det den variabel som står på parameterens plads i kaldet, der ændres (direkte i den hukommelsesplads variablen ligger). Det betyder også, at når der medsendes en referencevariabel (bla. objekter, strenge, arrays), så er det referencen der ændres, dvs. der peges på en anden forkomst af den medsendte type. Dette er fordi referencevariable i sig selv er referencer (navnet havde nok allerede afsløret dette) og det er altså pegepinden der ændres.

Det samme gælder for simple typer, hvor værdien ændres direkte i den hukommelsesposition, hvor variablen er defineret inden kaldet til funktionen.

Hukommelsespositionen kan dels være på "heapen", dels på stakken. Stakken benyttes hvis kaldet sker med en lokal variabel fra en anden funktion.

Fremmednøgletildeling i LINQ to SQL

Jeg har bakset lidt med en fejl, der opstod i forbindelse med opdatering af en LINQ to SQL-entity. Helt konkret drejer det sig om den lidet informative fejl: "Operation is not valid due to the current state of the object", som opstår i forbindelse med at jeg tildeler en værdi til en fremmednøgle i denne entity.

Setup'et

Jeg opererer med optimistisk samtidighed og et afkoblet datasæt, dvs. jeg læser objektet, lukker forbindelsen og skaber en ny forbindelse, når jeg skal opdatere data (et typisk scenarium for websites der involverer en bruger mellem læsning og opdatering). I forbindelse med selve opdateringer indlæser jeg først objektet (og beholder forbindelsen til databasen), opdaterer entity'objektet og opdaterer databasen. Jeg har altså gang i noget med at kopiere data fra entity over i et forretningsobjekt og tilbage igen ifm. opdatering.

Idet min entity læses sætter jeg nogle LoadOptions på forbindelsen, så relevante relaterede rækker fra andre tabeller også indlæses. Dette gøres af performancemæssige hensyn, da en reference til disse relaterede entities ville forårsage en "lazy loading" af data og dermed endnu et opslag i databasen.

Løsningen

Denne "eager loading" af relaterede data giver problemer, hvis jeg forsøger at opdatere fremmednøglen og ikke samtidig sætter det relaterede objekt til den nye entity (som nøglen også refererer til).

Jeg skal altså IKKE preloade relaterede tabeller, hvis jeg blot ønsker at benytte fremmednøglen til opdateringshandlinger.

Alternativt kunne jeg måske klare mig ved at benytte Attach i stedet for at læse entity inden opdatering. I dette scenarium ville der nok ikke ske en "eager load" af relaterede tabeller (jf. LoadOptions). Det har jeg dog ikke eksperimenteret med endnu...

Se også

http://www.faridesign.net/2010/11/linq-to-sql-operation-is-not-valid-due-to-the-current-state-of-the-object/