Det kanske inte är så dumt att hårdkoda endå...

Den israeliska Kodaren Ayende skrev för ett tag sen några intressanta inlägg där han framhöll att hårdkoda är det bästa sättet för att skapa lättföränderliga system. Stick och stäv mot de flesta programmerares uppfattning med andra ord. Och antagligen var han ute för att provocera en del och han lyckades reta upp rätt många utvecklare. Läs på Wikipedia om du vill veta mer om begreppet att hårdkoda.

Hur som helst blev en intressant omväxling kommentarer till inläggen och han förklarade mer hur han menade att för mycket konfigurering i system lätt kan leda till pannkaka. Kände mig lite träffad inte minst för att jag har just ett databasfält i en webbapp som heter likande som i hans exempel ("random_number") och insåg att konfigurering av onödiga värden lätt kan gå till överdrift.

Ayende berättar att han nyligen genomfört ett större projekt där han hårdkodade allt i små ddl:er och han tyckte det fungerade alldeles utmärkt. Fördelen menar han är att ju mindre värden som affärsfolket som ska använda systemet kan ändra desto bättre och mindre risk för att de matar in galna värden som är inkompatibla med varandra.

Sen är det ju ofta admin-interfacen som är det krävande jobbet när man bygger upp ett system. Ju minde värden kunden ska kunna ändra desto lättare går det ju att bygga färdigt systemet. Mycket jobb går annart till för att validerar input för vad nu användaren kan få för sig att trycka in i systemet.

Läs även det sista inlägget i hans "hårdkodar-serie" där han pratar om att när ett system skalar upp och fler regler läggs på så blir blir det ohållbart att ha för mycket konfigurerbara värden.

Intressant helt klart och något att tänkta på när man bygger system. Hårdkoda mera.

By Jesper Lind

Jaiku flyttas till Google App Engine

Mikroblogg-tjänsten Jaiku har under helgen varit nere för underhåll. Det har spekulerats ifall Google (som köpt tjänsten) håller på att flyttade den till deras Google App Engine och det verkar vara fallet. Jaikuinvites har kört en tracert som tyder på det i alla fall.

N borde det inte dröja länge innan de öppnar tjänsten för alla att registrera sig. Antagligen kommer de öven att koppla in den mot det vanliga google kontot/Gmail.

Hoppas nu bara att de drar igång på måndag igen, för det är ganska trist utan tjänsten som man börjat uppskatta mycket. Har blivit en del Twittrande istället för att kompensera.

By Jesper Lind

Jämnföra databasscheman och hålla ordning på versioner

När man utvecklar en webbapplikation (eller andra typer av program för den delen också) så är det ofta en stor utmaning att hålla strukturen på databaserna likadan. Man gör ändringar i sin orginalmodell och försöker ändra alla de databaser som är i drift enligt bästa förmåga. Hittills har jag inte haft något speciellt bra sätt att göra detta på utan det slutar ofta med felsökning steg för steg och ändra databasen manuellt. Tänkte här skriva om några sätt som kan förenkla detta jobb.

Lägga in databasskripten i källkodsprojektet

Ett sätt är att skripta ut hela databasen och sedan inkludera skripten i källkodsprojektet som Coding Horror förklarar. Inte helt på det klara om detta kan hjälpa en för att uppdatera befintliga databaser, men att ha strukturen i kod är ju ett bra första steg.

Använda sig av databas-migrering

Detta är ett koncept som funnits länge i Ruby On Rails-världen och innebär att varje förändring i databasen sparar i uppdateringsskript som man kan köra på sina databaser. 

Subsonic-teamet har nyligen inspirerats av Rails och lagt in liknande Migrations-funktionalitet i Subsonic. Har provat detta lite under sommaren och även fast det är väldigt ny teknik så verkar det fungera mycket bra.

Program för att Jämnföra databasscheman

Detta sätt tycker jag är det mest bekväma och innebär minst jobb. Man utgår helt enkels från sin orginalstruktur för databasen och jämför denna med de databaser som ska uppgraderas.

Denna funktionallitet finns i Visual Studio men bara i team-edition så den har jag inte testat själv. Verkar fungera fint och jag kan rekommendera läsning hos Emad Ibrahim som har provat på det.

Det smidigaste programmet som jag har provat är dock utan tvekan Redgate SQL Compare. Att jämföra två databaser går på nån minut och man får sedan SQL Skript redo att köra på den databas som ska uppgraderas. Har bara provat testversionen men funderar skarpt på att göra en investering i en licens.

Om du har några erfarenheter om hur man kan göra version-hantering av databaser lättare, så uppskattas kommentarer.

By Jesper Lind

Photosynth - supercool och nu öppen för allmänheten

http://www.codeodyssey.se/upload/resource/blog/tree-photosynth.jpg

Det var nära två år sen Microsoft visade upp demos på sin fototeknik Photosynth, som skapar 3D-miljöer från foton. Sen dess har den blivit väldigt hypad och bland annat används i ett avsnitt av den amerikanska tv-serien CSI.

Idag så släppte de äntligen tjänsten för allmänheten och man kan nu skapa ett konto där det går att ladda upp 20 MB bilder. Under dagen har tjänsten varit väldigt överbelastad och det gick inte ens att nå sidan för mig förrän nu på natten. I bloggen skriver de om hur hela sajten krashade av all trafik.

För tillfället går tjänsten bara att använda med Internet Explorer och Microsoft meddelar att det inte går att köra på Mac. Teamet bakom viritualiseringsmotorn VMware Fusion bevisade dock under dagen att de har fel och det kan jag också bekräffa.

Man behöver ha ett MS-passportkonto när man anmäler sig och processen gick snabbt och lätt. Testkörde en synth som ni kan se en screenshot på här ovan. Det är nästa omöjligt att visa hur det fungerar på en vanlig bild hur häftigt det är, så jag rekommenderar att ni besöker sajten själva för att verkligen se vad det handlar om. Bilderna man laddar upp fogas alltså tillsammans där de överlappar och programmet bygger upp en 3D-modell på det man fotat. Man kan sedan flyga runt i modellen med hjälp av tangenterna som i ett "first-person-shooter"-spel eller arrangera dem sida vid sida och annat skoj. Ett slagt superpanorama kan man säga.

Det gäller att alla bilderna i sin samling hänger ihop med tillräckligt många detaljer för att tjänsten ska kunna skapa en fullständig sammanfogning. I mitt fall så blev de tre fristående "öar" av bilder och den kunde bara sammanfoga samlingen till 56%. Jag använde bilder på ett träd och den fattade inte riktigt att jag hade bilder från olika håll av samma objekt. Men ändå är resultatet förbluffande häftigt – även när jag bara slängde upp den första grupp bilder jag hade i fotoalbumet. Tänk då vad man kan skapa om man lägger ner lite jobb på att förbereda bilderna och ta foton särskilt för detta ändamål.

Många har trott att själva processen att skapa 3D-miljöerna skulle ta väldigt lång tid, men jag tyckte det gick väldigt snabbt med de bilder jag testkörde med. Det som tog tid var själva uppladdningen till sajten och det kan man ju förstå, speciellt eftersom jag hade med några tunga rackare i mappen.

Photosynth är verkligen en höjdartjänst som jag vet att jag kommer ha mycket kul med. Riktigt roligt att den äntligen är igång.

By Jesper Lind

SQL-fråga med CROSS APPLY

APPLY (msdn) är ett nytt SQL-kommando i SQL Server från och med version 2005 och finns i två olika varianter, CROSS APPLY och OUTER APPLY.

Kan inte säga att jag förstått dessa funktionerna helt, men jag har hittat ett bra användningsområde för CROSS APPLY. Nämligen att kunna bygga upp VIEWs med data från en relationstabell och sammanfoga dessa i en ny kolumn separerade med valfritt tecken. Detta kallas tydligen "aggregate concatenation".

Läste även att denna metod går emot huvudideén för hur en relationsdatabas är tänkt att fungera, så har du ett behov att göra såna här SQL-frågor kan det vara snart att tänka om ifall det går att göra på ett bättre sätt. Eller kanske hämta ut datan separat och bygga ihop vyn i buisness-lagret.  

Kommer här visa hur det kan fungera genom ett exempel, här först databasdiagrammet.

 

Sen ett skript (skapat med SubSonic) som bygger upp hela databasen:

SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Products]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Products](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [ProductName] [nvarchar](150) COLLATE Finnish_Swedish_CI_AS NULL,
 CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
END
/****** Object:  Table [dbo].[Colors]    Script Date: 07/07/2008 16:58:21 ******/
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Colors]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Colors](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [ColorName] [nvarchar](150) COLLATE Finnish_Swedish_CI_AS NULL,
 CONSTRAINT [PK_Colors] PRIMARY KEY CLUSTERED
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
END
/****** Object:  Table [dbo].[ProductColor]    Script Date: 07/07/2008 16:58:21 ******/
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ProductColor]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[ProductColor](
    [ProductId] [int] NOT NULL,
    [ColorId] [int] NOT NULL,
 CONSTRAINT [PK_ProductColor] PRIMARY KEY CLUSTERED
(
    [ProductId] ASC,
    [ColorId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
END
/****** Object:  View [dbo].[ListOfColorsView]    Script Date: 07/07/2008 16:58:21 ******/
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
IF NOT EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[ListOfColorsView]'))
EXEC dbo.sp_executesql @statement = N'CREATE VIEW dbo.ListOfColorsView
AS
SELECT DISTINCT ProductName, ListOfColors = LEFT(o.list, LEN(o.list) - 1)
FROM         Products CROSS APPLY
                          (SELECT     CONVERT(VARCHAR(12), ColorName) + '','' AS [text()]
                            FROM          dbo.Colors c INNER JOIN
                                                   dbo.ProductColor ON c.Id = dbo.ProductColor.ColorId
                            WHERE      (dbo.ProductColor.ProductId = dbo.Products.Id)
                            ORDER BY c.ColorName FOR XML PATH('''')) o(list)
'
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_ProductColor_Colors]') AND parent_object_id = OBJECT_ID(N'[dbo].[ProductColor]'))
ALTER TABLE [dbo].[ProductColor]  WITH CHECK ADD  CONSTRAINT [FK_ProductColor_Colors] FOREIGN KEY([ColorId])
REFERENCES [Colors] ([Id])
ALTER TABLE [dbo].[ProductColor] CHECK CONSTRAINT [FK_ProductColor_Colors]
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_ProductColor_Products]') AND parent_object_id = OBJECT_ID(N'[dbo].[ProductColor]'))
ALTER TABLE [dbo].[ProductColor]  WITH CHECK ADD  CONSTRAINT [FK_ProductColor_Products] FOREIGN KEY([ProductId])
REFERENCES [Products] ([Id])
ALTER TABLE [dbo].[ProductColor] CHECK CONSTRAINT [FK_ProductColor_Products]

Och lite testdata:

ALTER TABLE [Colors] NOCHECK CONSTRAINT ALL
GO
ALTER TABLE [Colors] DISABLE TRIGGER ALL
GO

SET IDENTITY_INSERT [Colors] ON
PRINT 'Begin inserting data in Colors'
INSERT INTO [Colors] ([Id], [ColorName])
VALUES(1, 'Red')
INSERT INTO [Colors] ([Id], [ColorName])
VALUES(2, 'Blue')
INSERT INTO [Colors] ([Id], [ColorName])
VALUES(3, 'Green')
SET IDENTITY_INSERT [Colors] OFF
ALTER TABLE [Colors] CHECK CONSTRAINT ALL
GO

ALTER TABLE [Colors] ENABLE TRIGGER ALL
GO



ALTER TABLE [Products] NOCHECK CONSTRAINT ALL
GO
ALTER TABLE [Products] DISABLE TRIGGER ALL
GO

SET IDENTITY_INSERT [Products] ON
PRINT 'Begin inserting data in Products'
INSERT INTO [Products] ([Id], [ProductName])
VALUES(1, 'Test product 1')
INSERT INTO [Products] ([Id], [ProductName])
VALUES(2, 'Test product 2 - No Colors')
SET IDENTITY_INSERT [Products] OFF
ALTER TABLE [Products] CHECK CONSTRAINT ALL
GO

ALTER TABLE [Products] ENABLE TRIGGER ALL
GO



ALTER TABLE [ProductColor] NOCHECK CONSTRAINT ALL
GO
ALTER TABLE [ProductColor] DISABLE TRIGGER ALL
GO

PRINT 'Begin inserting data in ProductColor'
INSERT INTO [ProductColor] ([ProductId], [ColorId])
VALUES(1, 1)
INSERT INTO [ProductColor] ([ProductId], [ColorId])
VALUES(1, 2)
INSERT INTO [ProductColor] ([ProductId], [ColorId])
VALUES(1, 3)
ALTER TABLE [ProductColor] CHECK CONSTRAINT ALL
GO

ALTER TABLE [ProductColor] ENABLE TRIGGER ALL
GO

Det mest intressanta är ju själva vyn som separerar datan från relationstabellen med ett kommatecken mellan varje värde.

SELECT DISTINCT ProductName, ListOfColors = LEFT(o.list, LEN(o.list) - 1)
FROM         Products CROSS APPLY
                          (SELECT     CONVERT(VARCHAR(12), ColorName) + ',' AS [text()]
                            FROM          dbo.Colors c INNER JOIN
                                                   dbo.ProductColor ON c.Id = dbo.ProductColor.ColorId
                            WHERE      (dbo.ProductColor.ProductId = dbo.Products.Id)
                            ORDER BY c.ColorName FOR XML PATH('')) o(list)

Svaret från denna vyn ser ut så här:

Test product 1    Blue,Green,Red
Test product 2 - No Colors    NULL

Mer läsning:

http://zulfiqar.typepad.com/zulfiqars_web/2005/04/tsql_concatenat.html

http://blog.sqlauthority.com/2008/01/04/sql-server-2005-cross-apply/

By Jesper Lind

Video: "The Website is Down: Sales Guy vs. Web Dude"

Den här har nog redan gått några virala varv runt "the intertubes" men har du inte sett den rekommenderas den starkt. Varning för stundvis omogen humor och dåligt språk, men den är i många delar helt klockren. Vi har haft rätt mycket kul åt den här på kontoret och har svårt att oss på själva på allvar längre. Om vi nu någonsin har gjort det ;)

Orginalet finns på thewebsiteisdown.com och har på den senaste veckan blivit så populär så att själva sajten har krashat några gånger (ödets ironi...)

By Jesper Lind

ISAPI Rewrite 3 på Bineros servrar

Vi har börjat testa lite smått på några webbsajter att använda ISAPI-modulen som finns installerad hos Binero och det fungerar ganska bra. Vi har till och med fått igång ett första MVC-projekt på .NET 3.5. Riktigt kul med ett webbhotell som är framåt och förstår vilken teknik vi utvecklare vill ha.

Det tog lite tid att lista ut hur man fick igång det eftersom Bineros förklaring av Rewrite 3 inte är så utförlig. De har manualen på deras sajt så där kan man lära sig lite (funkar dock inte med Firefox 3). Helicon Tech som ligger bakom modulen rekommenderas också.

Mest hjälp för att få igång det hade jag av av några trådar på Aspsidan och webForum.

Något att tänka på är att de ändringar man gör i Script Mappings i Bineros kontrollpanel tar ett tag innan de slår igenom. Kommer ihåg den första kvällen då jag kämpade med det in på småtimmarna men hade ingen lycka. Provade mängder av olika konfigurationer och gav sist upp och bara lämnade det. Nästa morgon när jag laddade upp sidan så bara det fungerade!

Så nu ska jag förklara det jag kommit på hittills.

Det första man måste göra är att lägga till en *-mappning enligt följande.

Filetype: *
Executable: Aspnet2
is wildcard: false
is script engine: false
verify that file exists: false

När du ändå är där kan du även lägga till .asax eftersom Binero verkar ha glömt det i standardinställningen. Se bilden för exempel på de två inställningarna längst ner.

 

Sen är det dags att knåpa ihop en httpd.ini (uppdatering: filen ska ha namnet .htaccess och inget annat) som styr hur ISAPI Rewrite 3 ska uppföra sig. Här under ser ni våran som fungerar fint om man vill ta bort .aspx ändelserna. Jag har dock inte lyckats att exkludera vissa mappar, så om någon kan se vad det är för fel på sista raden som ska blocka mappen "MappSomInteSkaOmskrivas" så uppskattar jag tips. Uppdatering: Denna rad måste ju givetvis ligga överst för att de andra reglerna ska ignoreras. Har nu uppdaterat skriptet som det ska se ut och då funkar det mycket bättre.

# Helicon ISAPI_Rewrite configuration file
# Version 3.1.0.34

RewriteEngine on

#Exclude folder
RewriteRule /(?!(?:Images|Css|MappSomInteSkaOmskrivas)/.*).* - [L]

#Redirect extension requests to avoid duplicate content
RewriteRule ^([^?]+)\.aspx$ $1 [NC,R=301]

#Internally add extensions to request
RewriteCond %{REQUEST_FILENAME}.aspx -f
RewriteRule (.*) $1.aspx
By Jesper Lind

For-loop till SQL Server

Har funderat på ibland hur man kan göra foor-loop i SQL Server. Efter en sökning hittade jag svaret hos SQLAuthority som också visar upp exempel på break och continue.

I mitt fall ville jag köra en insert för varje loopning och mitt skript såg ut ungefär som följer. Variablen intFlag används också för att sätta in ett stigande heltal i tabellen.

DECLARE @intFlag INT
SET @intFlag = 1
WHILE (@intFlag <=100)
  BEGIN
        PRINT @intFlag

        INSERT INTO TheTable (IntValue,StringValue,CounterValue) VALUES (1,'MyDefaultString,@intFlag))
  SET @intFlag = @intFlag + 1
  END
GO

By Jesper Lind