Back to "WPF för webbkillen en berättelse om smärta, förtvivlan och lärande"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Imported mostlylucidcouk

WPF för webbkillen en berättelse om smärta, förtvivlan och lärande

Monday, 16 August 2010

Tja, OK Jag är över dramatisera det en smula :) Som du kanske har märkt från min senaste inlägg Jag har börjat arbeta med en del saker som ligger utanför min komfortzon. Jag gick nyligen med i ett företag som heter Vilda landlevande ryggradsdjur av arten Gallus domesticus, arbetar för en kille som heter @RobertTheGrey Ordförande, Det första projektet vi arbetar med är en fotoförsäljning webbplats ... du vet typ av som Getty Images / Smugmug / Flickr utom för direkt fotograf-kundförsäljning. heltidsutvecklare (hittills!) Jag har fått i uppdrag att bygga den stora majoriteten av de program (vare sig de webb, skrivbord, etc.) som vi behöver för att verksamheten ska fungera.

Självklart en av de mest kritiska bitar för en e-handel webbplats är grejer att sälja! I vårt fall är detta ett lager av riktigt högkvalitativa fotografier tagna av professionella fotografer runt om i världen. Första frågan; hur får vi dessa bilder på webbplatsen, och hur låter vi fotograferna lägga till information till sina fotografier för att låta dem hittas av folk som vill köpa dem? Tja, det är det första problemet jag har blivit ombedd att lösa!

När man arbetar på kraven, blev det snabbt klart att detta måste vara en desktop app, det behövde för att tillåta operationer på multi-megabyte fotografier, som behövs för att tillåta "tagging . Speciellt under inledande lastning, det är 10-100s av tusentals bilder som får arbeta på ... tvinga detta att vara online lägger till en otäck eftersläpning och gör uppgiften en smärta i rumpan att slutföra. Framför allt, det behövde vara pålitlig!
Nu, i mitt huvud detta ledde till ett antal beslut om de funktioner som appen skulle behöva för att stödja:

  1. Intuitiv, lyhörd UI; användarna är inte nödvändigtvis dator-savvy, behöver använda uppenbara UI metaforer och göra det snabbt nog att vara trevlig att använda.
  2. Behöver göra några offline bildbehandling; vi planerar att vara värd för detta på molnet....CPU tid kostar kontanter :)
  3. Ska fungera offline
  4. Måste byggas runt tillförlitlig uppladdning av multi-meg-filer (över potentiellt små anslutningsbandbredder).

Så, detta är de grundläggande behoven tekniskt sett leder detta mig till några tekniska val:

  1. Multi-threading / concurrency (som med någon riktig desktop app) är nyckelnsom är en anständig Desktop teknik
  2. Se ovan, vi måste "titta på nya filer anländer, göra miniatyrer transparent etc
  3. Så, det behöver någon form av uthållighet ... tillsammans med andra icke-kärna app krav (t.ex. taggning) kommer vi sannolikt behöver lite, lätt DB.
  4. Behöver "blockering" baserade uppladdningar med försök/verifiering av uppladdade objekt

Lyckligtvis har jag varit en dev ett bra tag och hade åtminstone någon idé var man ska börja den största utmaningen skulle vara att lära!
För det första är den uppenbara desktop plattform de jour ... detta är i huvudsak den nuvarande ersättningen för WinForms, det är mycket anpassningsbar men har en något ökänd brant inlärningskurva. Jag har spenderat lite pengar på böcker om WPF-värde, följde alla PDC presentationer, podcasts etc. på ämnet. Efter 3 veckor jag äntligen blir bekväm med det...
För det andra behövde jag en DB...behövs vara lätt, behövs för att stödja LINQ (vad, jag gillar LINQ :)), behövs för att stödja någon form av ORM-teknik.
Det mest uppenbara valet var SQL Server Kompakt utgåva, en typ av skära ner SQL-servern som är bra för desktop apps...hursomhelst...Jag kunde inte få det att fungera! Visar sig det är verkligen känslig för versionerna av olika installerade komponenter. Detta var en röd ljus för mig...Jag behövde DB för att vara så fristående som möjligt! Det lämnade en uppenbar val:
SQLite, från webbplatsen:

"SQLite är ett programvarubibliotek som implementerar en Elektroniska integrerade kretsar som kan arbeta utan yttre kraftkälla och som kan arbeta utan yttre kraftkälla, serverlös, nollinställning, Transaktionell SQL- databasmotorn. SQLite är de flesta utplacerade SQL- databasmotorn i världen. Källkoden för SQLite finns i offentlig verksamhet.’

Perfekt! För .NET stöd, Jag använde Leverantör av SQLite ADO.NET (det finns en C# endast SQLite port, men det är långsammare och inte behövs för mitt scenario. Deployment är bokstavligen inklusive en referens och skapa en fil ... nu har det en ganska allvarlig begränsning som jag kommer att täcka senare :)

Mina ORM val var nHibernate Ordförande Det finns stöd för SQLite på båda plattformarna.
Nu har jag använt nHibernate för en senaste projektet (disclaimer, Jag lämnade ganska tidigt i projektet :))) och en av de saker som irriterade mig var det stora antalet beroenden, och den skenbara bräcklighet över versioner av dessa beroenden. För en låg effekt desktop app, detta var bara för stort ett fotavtryck och för stor risk. Så jag var tvungen att räkna ut det.
Det här gav mig ett valEntity Framework (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework) (Entity Framework). Nu, detta var inte ett lätt val ... Jag har använt EF i det förflutna (känslorade med det) och fann det en smärtsam upplevelse ... (andra har också som ledde till detta. ). Nu, Jag kommer inte in i politiken bakom MS val att driva EF (och ja, det var ett politiskt val), men V1 är ganska buggy, och för mig åtminstone ganska onaturlig. Detta ledde till min andra lärande "möjlighet ... ... Master Entity Framework! När jag skriver detta, det är bokstavligen 2 timmar sedan jag arbetade ut min sista bit av EF smärta ... som ledde till denna modell:

Fånga

Ser enkelt ... ja, ja ... men det har tagit mig AGES att komma till detta ... jag vet SQL riktigt bra, C# verkligen bra, men blandningen av obskyra begrepp, dålig dokumentation och fördunklade felmeddelanden gör arbete / lärande EF alldeles för hårt! EF 4 (i VS 2010 / .NET 4) förbättrar detta ... och det kan inte komma snart nog!

Så, det har fungerat jag hoppas :)

Så, Jag nämnde tidigare att SQLite har lite av ett problem ... det inte riktigt gillar konvergens. Tja, det är en överdrift ... det är en multi-read, enda skrivsystem. När du inser att det är ett problem det är ganska lätt att lösa. Jag använder detta mönster:

public static void AddRange(IEnumerable<UploadFile> files)
        {
            using (PixEntities ent = new PixEntities())
            {
                using (new ReadLock(entityLock))
                {

                    using (new WriteLock(entityLock))
                    {
                        foreach (var file in files)
                        {
                            ent.AddToUploadFiles(file);
                            ent.SaveChanges();
                        }
                    }
                }
            }
        }
 

Som ni sett, detta har två huvudelement Jag använder en ReadLock (där jag normalt skulle läsa Från* DB, i detta fall umm, Jag gör inte :)) då jag använder en WriteLock för att linda någon operation där jag skriver till DB. Den observant kommer också att märka vad som ser ut som en bugg ... Jag gör ent.SaveChages () för varje iteration av loopen ... detta är en annan SQLite quirk, det inte stöder batch uppdateringar ... och ger ett konstigt fel om fil lås om du försöker :)

Den lilla låsklassen jag använder är från här, men jag har lagt det nedan för din användning i alla fall, detta är en del av flera delar av mina erfarenheter av att arbeta med Vilda landlevande ryggradsdjur av arten Gallus domesticusHåll ögonen öppna!

  public static class Locks
    {
        public static void GetReadLock(ReaderWriterLockSlim locks)
        {
            bool lockAcquired = false;
            while (!lockAcquired)
                lockAcquired = locks.TryEnterUpgradeableReadLock(1);
        }

        public static void GetReadOnlyLock(ReaderWriterLockSlim locks)
        {
            bool lockAcquired = false;
            while (!lockAcquired)
                lockAcquired = locks.TryEnterReadLock(1);
        }

        public static void GetWriteLock(ReaderWriterLockSlim locks)
        {
            bool lockAcquired = false;
            while (!lockAcquired)
                lockAcquired = locks.TryEnterWriteLock(1);
        }

        public static void ReleaseReadOnlyLock(ReaderWriterLockSlim locks)
        {
            if (locks.IsReadLockHeld)
                locks.ExitReadLock();
        }

        public static void ReleaseReadLock(ReaderWriterLockSlim locks)
        {
            if (locks.IsUpgradeableReadLockHeld)
                locks.ExitUpgradeableReadLock();
        }

        public static void ReleaseWriteLock(ReaderWriterLockSlim locks)
        {
            if (locks.IsWriteLockHeld)
                locks.ExitWriteLock();
        }

        public static void ReleaseLock(ReaderWriterLockSlim locks)
        {
            ReleaseWriteLock(locks);
            ReleaseReadLock(locks);
            ReleaseReadOnlyLock(locks);
        }

        public static ReaderWriterLockSlim GetLockInstance()
        {
            return GetLockInstance(LockRecursionPolicy.SupportsRecursion);
        }

        public static ReaderWriterLockSlim GetLockInstance(LockRecursionPolicy recursionPolicy)
        {
            return new ReaderWriterLockSlim(recursionPolicy);
        }
    }

    public abstract class BaseLock : IDisposable
    {
        protected ReaderWriterLockSlim _Locks;

        public BaseLock(ReaderWriterLockSlim locks)
        {
            _Locks = locks;
        }

        public abstract void Dispose();
    }

    public class ReadLock : BaseLock
    {
        public ReadLock(ReaderWriterLockSlim locks)
            : base(locks)
        {
            Locks.GetReadLock(this._Locks);
        }

        public override void Dispose()
        {
            Locks.ReleaseReadLock(this._Locks);
        }
    }

    public class ReadOnlyLock : BaseLock
    {
        public ReadOnlyLock(ReaderWriterLockSlim locks)
            : base(locks)
        {
            Locks.GetReadOnlyLock(this._Locks);
        }

        public override void Dispose()
        {
            Locks.ReleaseReadOnlyLock(this._Locks);
        }
    }

    public class WriteLock : BaseLock
    {
        public WriteLock(ReaderWriterLockSlim locks)
            : base(locks)
        {
            Locks.GetWriteLock(this._Locks);
        }

        public override void Dispose()
        {
            Locks.ReleaseWriteLock(this._Locks);
        }
    }
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.