Vítejte na blog.vyvojar.cz Přihlásit | Registrovat | Pomoc
Titulní Blogy Fotky Soubory

ZazaBlog

  • VPS hosting, který opravdu funguje

    Málokdy se stane, že dělám někomu reklamu, ale dobré zboží (a služby) je třeba chválit. To jsem onehdá potřeboval nějaký virtuální privátní server, na který bych si mohl nainstalovat Windows. Potřebuji totiž provozovat nějaký systém s minimálními výpadky konektivity.

    Brouzdal jsem po webu, až jsem narazil na nabídku firmy tvujweb.cz Mít VPS na VMware ESXi za 490,- Kč měsíčně s konektivitou 100 MB je legrační poplatek, navíc firma poskytuje třicetidenní lhůtu na vyzkoušení. Díky použití vSphere Clienta mám samozřejmě možnost s virtuální mašinou páchat kdejaké pokusy (přeinstalovat, připojit místní CD nebo ISO soubor do VPS a tak dále).

    Není to ale cena, co mě dostalo. Firma tvujweb.cz to evidentně s businessem myslí vážně, proto mají opravdu špičkovou podporu reagující v řádu minut. Považte sami:

    • Objednávku na VPS jsem zadával v 19:20 večer. Během pěti minut telefonát, VPS zřízen přesně za 27 minut od vyplnění objednávky.
    • Technický dotaz na navýšení výkonu VPS – 16:18, odpověď v 16:28
    • Administrativní dotaz k fakturaci 10:16, odpověď 10:22

    Toto považuji za příjemný nadstandard, u řady mnohem renomovanějších firem se dočkám odpovědi na e-maily třeba až po několika dnech.

    Negativa? Přemýšlím, a nic mě nenapadá. Snad jen to, že na VPS se trochu zpožďují hodiny a je třeba je častěji synchronizovat s časovým serverem ;-)

    Co Vy, hostujete někde, kde to má smysl?

  • Kam s konfigurací .NETové aplikace?

    Programátor píšící cokoli většího než je konzolová aplikace na pár řádečků dříve nebo později narazí na problém - kam ukládat konfigurační údaje, které aplikace pro svůj běh potřebuje. Způsobů je několik a každý z nich má své pro a proti. Pojďme se podívat na ty nejznámější z nich.

    INI soubory

    Už od dřevních dob používaly programy k uložení konfigurace tzv. ini soubory. Byly to textové soubory, typicky uložené v adresáři aplikace a směly obsahovat jednu či více sekcí a v každé sekci pak parametry. Čili, mohlo to vypadat nějak takto:

    [Config]
    server=localhost
    user=sa
    database=master
    
    [Export]
    format=txt
    Savepath=D:

    Jako hlavní výhodu ini souborů bych viděl jejich jednoduchost, jako hlavní nevýhodu vnímám to, že .NET je nepodporuje nativně. Je třeba použít staré triky a naimportovat si funkce z kernel32.dll:

    Private Declare Ansi Function GetPrivateProfileString _

      Lib "kernel32.dll" Alias "GetPrivateProfileStringA" _

      (ByVal lpApplicationName As String, _

      ByVal lpKeyName As String, ByVal lpDefault As String, _

      ByVal lpReturnedString As System.Text.StringBuilder, _

      ByVal nSize As Integer, ByVal lpFileName As String) _

      As Integer

    Nic moc, co?

    Registry Windows

    Ukládání informací do registrů je další dobrý a hojně používaný způsob zápisu konfigurace. Tento způsob má v .NET frameworku poměrně robustní podporu, takže můžete s pomocí Microsoft.Win32 psát něco ve smyslu:

    regKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\NODE\NODE\NODE", False)

    umisteni = regKey.GetValue("KEY")

    a obdobně pro ukládání. Ve prospěch používání registrů hovoří rovněž fakt, že do nich lze zapisovat již při instalaci (alespoň neznám žádný pokročilý instalátor, který by toto neuměl) a že pomocí nástroje regedit, který je k dispozici přímo v systému lze s registry manipulovat. Systém rovněž registry umí zálohovat a tak zazálohuje i Vaše nastavení, pokud jej registry obsahují.

    Co mě od registru odrazuje je jeho chaotičnost. Jistě, systémové věci jsou tam kde mají být, ale aplikace třetích stran obvykle zapisují do registru stylem “každý pes jiná ves”.

    VB.NET a jeho záložka Settings

    Pokud pracujete (jako já) ve VB.NET, je k dispozici elegantní způsob ukládání konfigurace pomocí předpřipravených nástrojů, a to ve formě My.Settings. Systém je klikací, vygenerovaný kód obsahuje typovou kontrolu a interně je konfigurace uložena v XML souboru kdesi v uživatelském profilu. XML může vypadat nějak takto:

    <?xml version="1.0" encoding="utf-8"?>

    <configuration>

        <userSettings>

            <Program.My.MySettings>

                <setting name="LastUpdateCheck" serializeAs="String">

                    <value>08/09/2009 14:30:53</value>

                </setting>

                <setting name="Verze" serializeAs="String">

                    <value>0.1</value>

                </setting>

            </Pracant.My.MySettings>

        </userSettings>

    </configuration>

    Sympatické na tomto řešení je, že všechen kód pro ukládání a načítání konfigurace se generuje sám, uživatel pouze nakliká název a typ proměnné. Co mě na tomto způsobu ukládání hodnot odrazuje jsou dvě věci:

    1. Pokud se změní číslo verze assembly nebo jí dáte strong name, konfigurace se automaticky zahodí. Lze sice konfiguraci upgradovat z nižší verze, nicméně není to až tak triviální.
    2. Co je horší, program si ukládá soubory user.config do složek s kryptickými jmény (soubor výše mám například uložen ve složce "c:\Users\[profil]\AppData\Local\[firma]\[program].exe_StrongName_zygbcdzgwep1celxzqmkzrjfy20v3pkk\0.1.0.0" a neznám způsob, jak tuto cestu programově zjistit. To mimo jiné znamená, že nevím, jak přečíst nastavení cizí aplikace, což by se v reále mnohokrát hodilo a což by například v případě použití registrů bylo naprosto triviální.

    Ukládání konfigurace do nějaké databáze

    Při ukládání konfigurace do databáze musí programátor typicky napsat nějaký kód a vymyslet způsob, jak budou hodnoty v databázi uloženy. Mezi největší přínosy tohoto řešení patří fakt, že konfiguraci v databázi lze sdílet mezi více uživateli programu – což při použití předchozích metod je někdy složité a jindy zcela nemožné.

    Mezi největší problémy patří to, že konfigurace z databáze je přístupná až po připojení k databázi. Pokud databáze ještě k dispozici není (například při startu programu nebo při selhání), konfiguraci nemusí jít načíst nebo uložit, a to může být problém.

    Něco vlastního

    Čas od času bývá k vidění rovněž nějaký vlastní systém – setkal jsem se například z projektem, který ukládal konfiguraci do XML souborů v adresáři aplikace. Vzhledem k tomu, že .NET obsahuje řadu nástrojů pro XML nemusí být tento způsob vůbec špatný. Výhodou je tedy jeho jednoduchost – co může být jednoduššího než:

    Dim oXS As XmlSerializer = New XmlSerializer(obj.GetType)

    Dim oStmW As StreamWriter

    oStmW = New StreamWriter("konfigurace.xml")

    oXS.Serialize(oStmW, obj)

    oStmW.Close()

    Na druhou stranu jako největší nevýhodu vnímám to, že tento způsob práce s konfigurací není nijak standardizován.

    A jak řešíte ukládání konfigurace Vy, milí čtenáři?

  • MD5 a útok hrubou silou? Proč ne…

    Rok od roku nám roste výpočetní výkon našich miláčků, a věci, které v minulosti byly takřka nemožné se stávají realitou. Jako cvičení jsem si vyzkoušel sestavit databázi řetězců a jejich MD5 hashů. Při rozumném objemu dat by se tak z MD5 hashe v databázi dal vyhledat původní řetězec, mě ale spíše zajímalo, jak rychlé sestavení takové databáze v reálných podmínkách bude.

    Po chvíli přemýšlení jsem se rozhodl vygenerovat všechny kombinace pětipísmenných řetězců (jen a-z), jejich MD5ky a uložit to do souboru. Výpočet MD5 je přímo ve frameworku, takže to nebyl problém. Jediné, co mě po chvíli experimetování zaujalo byl fakt, že šílenost s BitConverterem je o fous rychlejší než cokoli jiného ;-))

    Private Function GenerateHash(ByVal SourceText As String) As String

      Dim MD5 As New System.Security.Cryptography.MD5CryptoServiceProvider()

      Dim Hash As Byte() = MD5.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(SourceText))

      Return BitConverter.ToString(Hash).ToLower.Replace("-", "")

    End Function

    Takže hurá, pojďme sestavit souboreček obsahující řetězce a jejich hashe ve stylu:

    aaaaa;594f803b380a41396ed63dca39503542
    ...
    zzzzz;95ebc3c7b3b9f1d2c40fec14415d3cb8

    Kód, který to zajistí je celkem triviální a vyzná se v něm úplně každý:

    Dim stopky As Stopwatch = New Stopwatch

    stopky.Start()

    Using sw As StreamWriter = New StreamWriter("kombinace.txt")

      For i1 As Integer = 97 To 122

        For i2 As Integer = 97 To 122

          For i3 As Integer = 97 To 122

            For i4 As Integer = 97 To 122

              For i5 As Integer = 97 To 122

                Dim vysledek As String = Chr(i1) + Chr(i2) + Chr(i3) + Chr(i4) + Chr(i5)

                Dim vysledekamd5 As String = vysledek + ";" + GenerateHash(vysledek)

                sw.WriteLine(vysledekamd5)

              Next

            Next

          Next

        Next

      Next

    End Using

    stopky.Stop()

    Debug.Print(stopky.ElapsedMilliseconds / 1000)

    Hmm … na mém stroji 150 sekund a skoro půlgigový soubor. Aby taky ne, vždyť těch pětipísmenných kombinací je více než 11 000 000 (!). Výsledek se dá pohodlně narvat do databáze. Pokud sáhneme (například) po MySQL a využijeme brutálně rychlý příkaz LOAD DATA INFILE, nějak takto:

    use md5;
    set names 'utf8';
    load data infile 'kombinace.txt' into table md5 FIELDS terminated by ';';

    sesypou se nám data do databáze během nějakých pár vteřin. Mimochodem, režie úložiště není až tak drastická, pokud se použije MyIsam, zaberou data na disku jen o pár MB více než zdrojový plaintext soubor. Za nějakých 160 seknud máme tedy k dispozici databázi více než jedenácti milionů řetězců o délce 5 znaků a jejich MD5 hashů. Dál už by to tak jednoduché nebylo. Pokud bychom například chtěli napsat aplikaci, která by vyhledala v tabulce z hashe původní řetězec, bude třeba sloupec, v němž je uložen hash oindexovat. A tím nám databáze naroste, ehm, skoro na dvojnásobek.

    Co z toho vyplývá?

    Vlastně nic zajímavého. Pokud bychom brali v úvahu, že každý znak původního řetězce by mohl být nejen malé písmeno, ale i číslice, velké písmeno či znak s diakritikou, kombinací by narůstalo. Při 128 možnostech na znak by existovalo 34 miliard pětipísmenných kombinací, a to by se uvedeným tempem generovalo skoro 8 dnů. Databáze by byla velká asi tak 1 TB ;-))

    Samozřejmě, lidé mohou mít šesti, sedmi a vícepísmenné řetězce. Zjistit z jejich MD5 hashů původní řetězec by se pomocí sestavení databáze neúměrně časově prodloužilo. Nicméně, existují ON-LINE služby, které se o něco takového pokoušejí. Jedna z nich je například zde: http://md5.rednoize.com/

  • WPF a data – také se Vám zdá, že ještě potřebuje dozrát?

    K napsání této krátké úvahy mě inspiroval nedávný článek zde o tom, jak je ve WPF jednoduché udělat Master – Detail binding. Takže se zdá, že pracovat s daty bude hračka. Dokonce lze využít LINQ to SQL (de facto vlastně LINQ to COKOLI) a “jenom” to pospojovat.

    Jenomže ono to nebude tak jednoduché. XAML soubory nebývají na dva řádky. Tohle je, na ukázku, XAML soubor z toho odkazu výše. Přestože příklad je primitivní, pár písmenek se tam najde. A je třeba tam dopsat informace o propojení na zdroje dat.

    <Window x:Class="MasterDetailView.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="Master - Detail View" Height="400">

        <Grid x:Name="gridMasterDetail">

            <Grid.ColumnDefinitions>

                <ColumnDefinition Width="200" />

                <ColumnDefinition Width="200" />

                <ColumnDefinition Width="200" />

                <ColumnDefinition Width="200" />

                <ColumnDefinition Width="200" />

            </Grid.ColumnDefinitions>

            <DockPanel Grid.Column="0">

                <Label DockPanel.Dock="Top">Region</Label>

                <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"/>

            </DockPanel>

            <DockPanel Grid.Column="1">

                <Label DockPanel.Dock="Top">Division</Label>

                <ListBox ItemsSource="{Binding Path=Divisions}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name" />

            </DockPanel>

            <DockPanel Grid.Column="2">

                <Label DockPanel.Dock="Top">City</Label>

                <ListBox ItemsSource="{Binding Path=Divisions/Cities}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"/>

            </DockPanel>

            <DockPanel Grid.Column="3">

                <Label DockPanel.Dock="Top">District</Label>

                <ListBox ItemsSource="{Binding Path=Divisions/Cities/Districts}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name" />

            </DockPanel>

            <DockPanel Grid.Column="4">

                <Label DockPanel.Dock="Top">Street</Label>

                <ListBox ItemsSource="{Binding Path=Divisions/Cities/Districts/Streets}" DisplayMemberPath="Name" />

            </DockPanel>

        </Grid>

    </Window>

    Databázové aplikace, které typicky píšu, mívají v tabulce nějakých 35 sloupců, takže na formulářích je při bindingu docela nacpáno ;-)) Zatímco nápad s Binding Path je prostě skvělý, docela mi chybí možnost ty informace o Itemssource a DisplayMemberPath nějak smysluplně dostat do XAML souboru. Třeba pomocí okna Properties.

    Dělal už někdo ze čtenářů nějakou Databázovou WPF aplikaci? Jak jste to řešili? A nevíte, jestli je ve VS 2010 v tomto ohledu nějak chudákovi vývojáři práce usnadněna?

  • Jak veliké jsou databáze, se kterými pracujete?

    Svět se asi zbláznil. Možná jsem to jen nepostřehl a do databází se už cpe úplně všechno, nebo jsem jenom přestal chápat určitou skupinu lidí. Jde o to, že na jednom serveru teď vášnivě diskutují o tom, jestli je nebo není zásadní omezení ve velikosti databáze 4 GB u Microsoft SQL serveru express edice.

    Čili se pokusím o takovou minianketu – jak velké jsou Vaše “průměrné” databáze a jaká je v praxi největší databáze, kterou jste viděli / napsali / spravovali? Zajímá mě typ DB a velikost v MB.

    Ať se snažím jak se snažím, u mě “vítězí” MSSQL s asi 300 MB, průměrně ale do 100 MB.

  • Databázové prapodivnosti – ALTER TABLE

    Může Vás chronicky známý a snad všemi programátory mnohokrát použitý DDL příkaz ALTER TABLE nějak zaskočit? Popravdě řečeno, po nějakých těch letech práce s různými databázemi jsem si myslel, že ne. ALTER TABLE mění strukturu tabulky, tečka. Nic víc, nic míň. Ovšem i tady platí ono ”po několika letech dřiny by ses mohl stát docela slušným začátečníkem”. A databázi používej radši jen jednu, protože když jich budeš znát víc, zkomplikuješ si život.

    Nuže, tedy. Mějme tabulku v nějaké databázi

    create table neco (id integer, nazev varchar (100))

    Tabulky jsou tu samozřejmě od toho, aby v nich byla nějaká ta data, nuže co třeba

    insert into neco (id, nazev) values (1, ‘text’)

    Zatím v pohodě, že? Pojďme teď dojít k závěru, že naše tabulka potřebuje nějakým způsobem rozšířit o, řekněme, sloupec “popis”. A protože nemáme rádi hodnoty NULL, vydefinujme jej jako:

    alter table neco add popis varchar (50) not null

    uáááá! Chceme do tabulky s existujícími daty vložit nový sloupec, který nepovoluje hodnoty null, ale nedefinovali jsme žádný DEFAULT. Tady se databáze chovají různě:

    MSSQL – odmítne v takovém případě sloupec vytvořit

    MySQL – sloupec vytvoří a pro existující záznamy do něj vloží prázdný řetězec

    Firebird – sloupec vytvoří a a pro existující záznamy do něj vloží hodnotu NULL (!)

    V diskusi se můžete vyřádit komentáři o tom,

    1. Jak to dělá “Vaše” databáze
    2. Který z postupů uvedených výše považujete za “správný”

    Já si to okomentuji rovnou: Přístup MSSQL mi přijde “správný” v tom smyslu, že jsem nedefinoval DEFAULT a tudíž můj požadavek nelze splnit. Přístup MySQL mi přijde “trochu špatný” v tom, že se databáze pokouší myslet za programátora a přístup Firebirdu mi přijde “hodně špatný” protože databáze se aktivně podílí na vytvoření nekonzistence.

  • Firebird budiž pochválen

    Před asi tak deseti lety jsem programoval jedno databázové řešení, které bylo založené na VB6 (znalci si jistě vzpomenou) a na Accessu. Z původně malé, embedded databáze se během vývoje vyklubalo něco, co mělo zvládat práci zhruba pěti, šesti uživatelů současně a databáze v některých tabulkách měla mít několik desítek tisíc řádků. Samozřejmě, že to v reále nefungovalo, na něco takového Access nebyl stavěný. Databáze padala jak švestky na podzim, projekt byl přepsán do MSSQL a já si zapsal za uši, že embedded databáze je sprostý výraz, který se v mojí přítomnosti nesmí vyslovovat.

    Před nedávnem jsem opět dostal zakázku na řešení vyžadující vloženou databázi. Již delší dobu pošilhávám po tom, že vyzkouším Firebird, nuže vyzkoušel jsem, a tady jsou moje postřehy.

    Instalace a zprovoznění

    Nebudu dopodrobna popisovat, co je třeba udělat, pokud chcete k .NETovému projektu přibalit knihovny potřebné pro běh embedded verze Firebirdu, to už za mě udělali jiní. V zásadě jde o to přibalit k projektu jednak knihovny samotného firebirdu, a jednak knihovny ADO.NET data providera. Je ale fakt, že to skutečně funguje. Pokud někdo, stejně jako já, používá pro instalaci vynikající InnoSetup, postačí do skriptu dopsat něco ve smyslu:

    Source: fbclient.dll; DestDir: {app}
    Source: icudt30.dll; DestDir: {app}
    Source: icuin30.dll; DestDir: {app}
    Source: icuuc30.dll; DestDir: {app}
    Source: intl\fbintl.conf; DestDir: {app}\intl
    Source: intl\fbintl.dll; DestDir: {app}\intl
    Source: FirebirdSql.Data.FirebirdClient.dll; DestDir: {app}

    Firebird má 32bitové i 64bitové verze, takže nepoplést. Dále je třeba v projektu mít nějakou tu databázi. Ta se dá buď přibalit vytvořená, nebo vytvořit pomocí příkazu:

    FbConnection.CreateDatabase(connectionString)

    A pokud potřebujete ze skriptu vygenerovat nějaké databázové objekty, tak Vám poslouží objekt FBScript

    Dim sc As FbScript = New FbScript(Obsah)

    sc.Parse()

    Dim fbe As FbBatchExecution = New FbBatchExecution(conn)

    For Each xcmd As String In sc.Results

    fbe.SqlStatements.Add(xcmd)

    Next

    fbe.Execute()

    Co Firebird umí

    Asi by bylo jednodušší popsat, co neumí, ale nic mě nenapadá. Má pohledy, uložené procedury, spouště, cizí klíče, generátory … prostě všechno, co si budete přát. Navíc, embedded verze není nijak okleštěná oproti “velkému” Firebirdu. A killer feature – pokud se Vám projekt rozroste, prostě vyměníte connectionstring a namísto “malé” databáze použijete databázový server. V aplikaci jinak není třeba upravovat nic.

    Reálný provoz

    Co mě ale dostalo, je absolutní pohoda při práci s tímto řešením. Vytvořil jsem zátěžové testy, naspal jsem do embedded databáze desítky megabajtů dat a ono to stále fungovalo. Fungovalo to všude od Windows 98 po Windows seven, a celkem konzistentní byl i výkon. Jediná věc, na kterou jsem narazil byla ta, že opakované select dotazy je z hlediska výkonu lepší přepsat do uložených procedur. Ale to platí pro jakoukoli databázi.

    Z nějakého důvodu jsem potřeboval pracovat s BLOBy, rovněž naprostá pohoda. Existuje dokonce názor, že práce s Firebirdem je v tomto ohledu výkonnější než práce s filesystémem. Nevím, nezkoušel jsem.

    Co mě štve

    Abych jen nepěl chválu – existují i nějaké maličkosti, na které při práci s Firebirdem narazíte. Já řešil třeba tohle:

    1. Internetové stránky http://www.firebirdsql.org/ vypadají jak z roku 1999.
    2. DDL je místy zkostnatělý a tvrdohlavý.
      alter table texty add popis varchar (200) not null default '';
      neprojde, ale
      alter table texty add popis varchar (200) default '' not null;
      projde. Jde o zvyk.
    3. Občas musíte hledat dokumentaci. Tak třeba z VB.NET získat hodnotu výstupního parametru z uložené procedury se mi podařilo až metodou pokus-omyl. Za to samozřejmě nemůžou ani tak vývojáři firebirdu, jako spíš vývojáři ADo.NET pro firebird.
    4. Nevím, co použít na správu databáze. Zkouším IBExpert, ale management studio to není ;-) . Nevíte někdo o něčem lepším?
  • Komponenta WebBrowser je … prostě podivná

    Asi jste to zažili. Potřebujete přidat do WinForms internetový prohlížeč, takže sáhnete po ovládacím prvku WebBrowser, přetáhnete jej do okna, nastavíte pár vlastností a je hotovo. Nebo není?

    Komponenta sama o sobě by měla být pochopitelná. Potřebuji přejít na URL? Metoda Navigate:

    Browser1.Navigate ("http://www.seznam.cz")

     

    Potřebuji namísto toho načíst html stránku z disku, databáze nebo z proměnné? Mělo by stačit

    Browser1.DocumentText = "bla bla bla"

    Jenomže, ehm, jaksi to nestačí. Vypozoroval jsem zvláštní věci:

    1. Pokud je DocumentText relativně krátký, řekněme do jednoho kilobajtu, proběhne všechno jak má. Dokument obsahuje výsledný HTML, jak by asi každý čekal.
    2. Pokud je ale řetězec, který se snažím načíst do DocumentText delší, třeba 5 kB, začnou se dít věci. Na Windows Vista a na Windows XP funguje všechno v pohodě, na Windows 2000 se namísto obsahu stránky zobrazí její zdrojový kód.

    Vyzkoušel jsem snad úplně všechno, a všechno marné. Nic nevedlo k výsledku:

    1. Nainstaloval jsem do Virtual PC čistou kopii systému se všemi aktualizacemi. Nic.
    2. Přeinstaloval jsem .NET Framework. Nic.
    3. Zkrátil jsem text – začalo to fungovat, pokud jsem jej zvětšil na původní velikost, zase se namísto výsledku začal zobrazovat zdroják.
    4. Ověřil jsem kódování a validitu toho HTML souboru, resp. řetězce reprezentujících HTML, nic

    Nakonec pomohlo namísto DocumentText použít DocumentStream, v principu nějak takhle:

    Dim encoding As System.Text.Encoding = System.Text.Encoding.GetEncoding(1250)

    Dim bytes As Byte() = encoding.GetBytes(value)

    Dim ms As MemoryStream = New MemoryStream(bytes)

    Browser1.DocumentStream = CType(ms, Stream)

    Tohle mi funguje napříč všemi testovanými OS a při jakékoli délce textu, kterou se tomu snažím vecpat.

    Nemáte někdo alespoň trochu racionální vysvětlení, proč se tak tato ošklivá komponenta chová?

  • Broadband router ASUS WL-520GC je šunt, nekupovat

    Říkám si to pokaždé a pokaždé se nechám nachytat: Levné síťové prvky jsou k ničemu. To jsem si takhle jednou namísto stařičkého již Pentia III s Linuxem uvnitř, který mi routoval řadu let a v poslední době fungoval spíš jako topení, pořídil broadband router ASUS WL-520GC. Tahle zběsilost má WAN port a 4 LAN porty, Wireless, samozřejmě NAT a firewall, takže namísto zapnutého písidla – routeru by měla stačit.

    asus

    Šupky dupky, nakolonovat MAC adresu a hurá na web (mám, mimochodem, kabelovku od UPC). Nějakou dobu jelo všechno dobře, web šlapal, dalo se stahovat, ale problém nastal v momentě, kdy jsem potřeboval využít nějaké služby v místní síti zvenčí. Jat trestuhodně nemístnou naivitou jsem nastavil NAT, povolil porty na firewallu, vyzkoušel a doufal, že je vše Ok.

    Jenomže pokud se na daný port připojilo zvenčí 4-5 konexí, router přestal stíhat. Vyřídit spojení trvalo třeba vteřinu, nebo taky 20. Zajímavé. Nejdřív jsem pochopitelně podezříval kdeco, teroristy počínaje a providerem konče, až nakonec jsem se podíval na zoubek onomu routeru. Je to firmwarem. Pokud si přeflashujete na, řekněme, DD-WRT, problém zmizí.

    Poučení z krizového vývoje? Na SO-HO připojení sítě o třech počítačích do internetu prostě krabička za devět stovek fakt nestačí ;-)

  • Úskalí LINQ to SQL podruhé

    Minule jsem si postěžoval na určitou nekonzistenci, s jakou LINQ to SQL zachází s DEFAULT hodnotami při vytváření nového záznamu v databázi. Pokud by to bylo jediné omezení této jinak skvělé technologie, pravděpodobně by to bylo ještě poměrně veselé. Věci jsou však horší, než se čekalo.

    Rozšiřme nyní trochu příklad z předchozího dílu této "stěžovací si minisérie" a k naší tabulce vytvořme nějakou ukázkovou, primitivní relaci. Máme faktury, tak třeba položky:

    CREATE TABLE POLOZKY(

        ID int NOT NULL IDENTITY (1,1),

        FAKTURA int NOT NULL,

        NAZEV varchar (50) NOT NULL DEFAULT 0,

        CENA money NOT NULL DEFAULT 0,

        CONSTRAINT PK_POLOZKY PRIMARY KEY CLUSTERED (ID)   

    )

     

    ALTER TABLE POLOZKY ADD CONSTRAINT FK_POLOZKA_FAKTURA

    FOREIGN KEY (FAKTURA) REFERENCES FAKTURA (ID)

    Spusťme si projekt, a do dbml souboru si přetáhněme naši novou tabulku POLOZKY. Fajn, program pozná, že jsou tabulky v realci. (V tabulce POLOZKY, mimochodem, je nutné míti primární klíč, jinak LINQ to SQL relaci pozná, ale nepoužije.) Zatím to vypadá moc dobře, v dbml souboru tedy takto:

    <Table Name="dbo.FAKTURA" Member="FAKTURAs">

      <Type Name="FAKTURA">

        <Column Name="ID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />

        <Column Name="DATUM" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />

        <Column Name="CENA" Type="System.Decimal" DbType="Money NOT NULL" CanBeNull="false" />

        <Column Name="ODBERATEL" Type="System.String" DbType="VarChar(50) NOT NULL" CanBeNull="false" />

        <Association Name="FAKTURA_POLOZKY" Member="POLOZKies" ThisKey="ID" OtherKey="FAKTURA" Type="POLOZKY" />

      </Type>

    </Table>

    <Table Name="dbo.POLOZKY" Member="POLOZKies">

      <Type Name="POLOZKY">

        <Column Name="ID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />

        <Column Name="FAKTURA" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />

        <Column Name="NAZEV" Type="System.String" DbType="VarChar(50) NOT NULL" CanBeNull="false" />

        <Column Name="CENA" Type="System.Decimal" DbType="Money NOT NULL" CanBeNull="false" />

        <Association Name="FAKTURA_POLOZKY" Member="FAKTURA1" ThisKey="FAKTURA" OtherKey="ID" Type="FAKTURA" IsForeignKey="true" />

      </Type>

    </Table>

    Dále, mějme nějaký triviální kód pro vložení faktury a položky, řekněme:

    Dim myFaktura As FAKTURA = New FAKTURA

    myFaktura.DATUM = #1/1/2008#

    myFaktura.CENA = 500

    myFaktura.ODBERATEL = "Pepa"

     

    Dim myPolozka As POLOZKY = New POLOZKY

    myPolozka.CENA = 500

    myPolozka.NAZEV = "Název zboží"

     

    myFaktura.POLOZKies.Add(myPolozka)

     

    d.FAKTURAs.InsertOnSubmit(myFaktura)

     

    d.SubmitChanges()

    Očekávali byste v tomto kódu nějakou chybu? Ne, a dobře děláte. Toto projde naprosto bez problémů. Otázka je, co se stane, pokud dojdeme dřív nebo později k závěru, že ta položka nebo položky by tam být neměly. Takže, pojďme následně zavolat:

    myFaktura.POLOZKies.Clear()

    d.SubmitChanges()

    Nevím jak Vám, ale mě tato operace přijde poměrně standardní. Jaké ovšem zažijeme překvapení, když namísto smazání položky (položek) skončí kód chybou "Invalid operation exception" ve smyslu:

    An attempt was made to remove a relationship between a FAKTURA and a POLOZKY. However, one of the relationship's foreign keys (POLOZKY.FAKTURA) cannot be set to null.

    Řešení je hned několik, ale všechna mají své pro a proti:

    1. Především můžeme pole FAKTURA v tabulce POLOZKY definovat jako NULLABLE. Na tomto řešení se mě (a asi žádnému rozumnému databázistovi) nelíbí to, že v tabulce POLOZKY zůstanou osiřelé záznamy. A občas jsme postaveni před hotovou databázi, kterou nelze měnit ;-(
    2. lze ručně editovat soubor a na příslušné místo dopsat instrukci DeleteOnNull=True

      <Table Name="dbo.POLOZKY" Member="POLOZKies">

        <Type Name="POLOZKY">

          <Column Name="ID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />

          <Column Name="FAKTURA" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />

          <Column Name="NAZEV" Type="System.String" DbType="VarChar(50) NOT NULL" CanBeNull="false" />

          <Column Name="CENA" Type="System.Decimal" DbType="Money NOT NULL" CanBeNull="false" />

          <Association Name="FAKTURA_POLOZKY" Member="FAKTURA1" ThisKey="FAKTURA" OtherKey="ID" Type="FAKTURA" IsForeignKey="true" DeleteOnNull="true"/>

        </Type>

      </Table>

      Na tom se mi nelíbí především nutnost zasahovat ručně do souboru vytvořeného Visual Studiem, nebo
    3. Přidat do definice cizího klíče ON DELETE CASCADE,

      ALTER TABLE POLOZKY ADD CONSTRAINT FK_POLOZKA_FAKTURA

      FOREIGN KEY (FAKTURA) REFERENCES FAKTURA (ID) ON DELETE CASCADE

      což povede k tomu, že se DeleteOnNull nastaví do DBML souboru samo. Na tom se mi nelíbí především ta do očí bijící nekoncepčnost. Proč musím měnit pravidlo pro mazání PARENT záznamu, jestliže chci pouze smazat CHILD záznam?

    Rozumné řešení nemám. Používám bod 2) s tím, že pokud je třeba tabulku přegenerova, musím si to tam znovu dopsat. Jak to řešíte Vy ostatní?

    Posted 25. února 2009 14:45 by xzajic | 2 Comments
    Vedeno pod:
  • LINQ to SQL ano, ale ...

    LINQ to SQL je skvělá volba. Bezpochyby. I naprostý amatér si "nakliká" DBML soubor a může používat objekty namísto toho, aby pracoval přímo s ADO.NET. Myšlenka jistě dobrá. Na LINQ to SQL nicméně existuje několik věcí, které mě při jeho používání nevýslovně štvou.

    Výchozí hodnoty po inicializaci

    Při návrhu databáze se pochopitelně vyhýbám hodnotám NULL všude, kde můžu, a často používám DEFAULT hodnoty. Takže, fragment, dejme tomu, tabulky faktur by mohl vypadat nějak takhle:

    CREATE TABLE FAKTURA(

        ID int NOT NULL IDENTITY (1,1),

        DATUM datetime NOT NULL DEFAULT getdate(),

        CENA money NOT NULL DEFAULT 0,

        ODBERATEL varchar (50) NOT NULL DEFAULT 0,

        CONSTRAINT PK_FAKTURA PRIMARY KEY CLUSTERED (ID)

    )

    Z čehož mi LINQ vygeneruje DBML soubor s přibližně tímto obsahem:

    <Table Name="dbo.FAKTURA" Member="FAKTURAs">

      <Type Name="FAKTURA">

        <Column Name="ID" AutoSync="Never" Type="System.Int32" DbType="Int NOT NULL" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />

        <Column Name="DATUM" AutoSync="Never" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />

        <Column Name="CENA" Type="System.Decimal" DbType="Money NOT NULL" CanBeNull="false" />

        <Column Name="ODBERATEL" Type="System.String" DbType="VarChar(50) NOT NULL" CanBeNull="false" />

      </Type>

    </Table>

    Poměrně hloupé, není liž pravda? Pochopitelně, při jakémkoli pokusu vytvořit fakturu následujícím způsobem skončíte na chybě:

    Dim myFaktura As FAKTURA = New FAKTURA

    myFaktura.DATUM = #1/1/2008#

    d.FAKTURAs.InsertOnSubmit(myFaktura)

    Try

      d.SubmitChanges()

    Catch ex As Exception

      Debug.Print(ex.ToString)

    End Try

    z toho prostého důvodu, že jste neinicializovali některé vlastnosti, LINQ to SQL je nedoplnil, pokusil se poslat do databáze v jistých sloupcích hodnoty NULL a ono to selhalo. Řešení je sice prosté, máme partial classes, kde se to dá nainicializovat, ale kdo se s tím má otravovat?

    Partial Class Faktura

      Private Sub OnCreated()

        Me.CENA = 0

        Me.DATUM = Today

        Me.ODBERATEL = ""

      End Sub

    End Class

    Máte někdo řešení, jak z toho ven?

    Posted 11. února 2009 21:32 by xzajic | 5 Comments
    Vedeno pod:
Powered by Community Server (Personal Edition), by Telligent Systems
Vyvojar.cz na prodej!